Skip to content

8341641: Make %APPDATA% and %LOCALAPPDATA% env variables available in *.cfg files #23923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* Class to replace tokens in strings.
* <p>
* Single instance holds a list of tokens. Tokens can be substrings of each other.
* The implementation performs greedy replacement: longer tokens are replaced first.
*/
public final class TokenReplace {

private record TokenCut(String[] main, String[] sub) {
Expand Down Expand Up @@ -103,7 +109,7 @@ public TokenReplace(String... tokens) {
this.tokens = tokens;
regexps = new ArrayList<>();

for(;;) {
for (;;) {
final var tokenCut = TokenCut.createFromOrderedTokens(tokens);
regexps.add(Pattern.compile(Stream.of(tokenCut.main()).map(Pattern::quote).collect(joining("|", "(", ")"))));

Expand Down Expand Up @@ -153,12 +159,12 @@ public int hashCode() {
@Override
public boolean equals(Object obj) {
// Auto generated code
if (this == obj)
if (this == obj) {
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
TokenReplace other = (TokenReplace) obj;
return Arrays.equals(tokens, other.tokens);
}
Expand Down
23 changes: 23 additions & 0 deletions src/jdk.jpackage/share/man/jpackage.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,35 @@ The `jpackage` tool will take as input a Java application and a Java run-time im

This option can be used multiple times.

Value can contain substrings that will be expanded at runtime.
Two types of such substrings are supported: environment variables
and "APPDIR", "BINDIR", and "ROOTDIR" tokens.

An expandable substring should be enclosed between the dollar
sign character ($) and the first following non-alphanumeric
character. Alternatively, it can be enclosed between "${" and "}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This did not sit well with pandoc, that reports:

[WARNING] Could not convert TeX math ) and the first following non-alphanumeric character. Alternatively, it can be enclosed between ", rendering as TeX:
  e enclosed between "
                     ^
  unexpected '"'
  expecting "\\bangle", "\\brace", "\\brack", "\\choose", "\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle", "{", "\\operatorname", letter, digit, ".", "!", "'", "''", "'''", "''''", "*", "+", ",", "-", ".", "/", ":", ":=", ";", "<", "=", ">", "?", "@", "~", "_", "^", "\\left", "\\", "\\hyperref" or end of input

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing a possible fix would be to use

`${`

or

"`${`"

instead.

Alternatively, we need to tell pandoc to not try and use any encoded TeX math in markdown files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed https://bugs.openjdk.org/browse/JDK-8354320 to fix the warning

Copy link
Member Author

@alexeysemenyukoracle alexeysemenyukoracle Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding backslash character (\) before the dollar character ("${" -> "\${") suppressed the warning, and the man page looks as expected.

PR #24585

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said in that PR, yes, it silences the warning, but it is not standard markdown, and will be rendered incorrectly by a standard markdown renderer.

substrings.

Expandable substrings are case-sensitive on Unix and
case-insensitive on Windows. No string expansion occurs if the
referenced environment variable is undefined.

Environment variables with names "APPDIR", "BINDIR", and "ROOTDIR"
will be ignored, and these expandable substrings will be
replaced by values calculated by the app launcher.

Prefix the dollar sign character with the backslash character (\)
to prevent substring expansion.

<a id="option-java-options">`--java-options` *options*</a>

: Options to pass to the Java runtime

This option can be used multiple times.

Value can contain substrings that will be substituted at runtime,
such as for the --arguments option.

<a id="option-main-class">`--main-class` *class-name*</a>

: Qualified name of the application main class to execute
Expand Down
8 changes: 4 additions & 4 deletions src/jdk.jpackage/share/native/applauncher/AppLauncher.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -164,9 +164,9 @@ CfgFile* AppLauncher::createCfgFile() const {
<< cfgFilePath << "\"");

CfgFile::Macros macros;
macros[_T("$APPDIR")] = appDirPath;
macros[_T("$BINDIR")] = FileUtils::dirname(launcherPath);
macros[_T("$ROOTDIR")] = imageRoot;
macros[_T("APPDIR")] = appDirPath;
macros[_T("BINDIR")] = FileUtils::dirname(launcherPath);
macros[_T("ROOTDIR")] = imageRoot;
std::unique_ptr<CfgFile> dummy(new CfgFile());
CfgFile::load(cfgFilePath).expandMacros(macros).swap(*dummy);
return dummy.release();
Expand Down
89 changes: 68 additions & 21 deletions src/jdk.jpackage/share/native/applauncher/CfgFile.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,13 +25,16 @@

#include "kludge_c++11.h"

#include <set>
#include <fstream>
#include <algorithm>
#include "CfgFile.h"
#include "SysInfo.h"
#include "Log.h"
#include "Toolbox.h"
#include "FileUtils.h"
#include "ErrorHandling.h"
#include "StringProcessing.h"


const CfgFile::Properties& CfgFile::getProperties(
Expand Down Expand Up @@ -60,38 +63,82 @@ CfgFile& CfgFile::setPropertyValue(const SectionName& sectionName,

namespace {

tstring expandMacros(const tstring& str, const CfgFile::Macros& macros) {
tstring reply = str;
CfgFile::Macros::const_iterator it = macros.begin();
const CfgFile::Macros::const_iterator end = macros.end();
for (; it != end; ++it) {
reply = tstrings::replace(reply, it->first, it->second);
template <typename CfgFileType, typename OpType>
void iterateProperties(CfgFileType& cfgFile, OpType& op) {
for (auto mapIt = cfgFile.begin(), mapEnd = cfgFile.end(); mapIt != mapEnd; ++mapIt) {
for (auto propertyIt = mapIt->second.begin(), propertyEnd = mapIt->second.end(); propertyIt != propertyEnd; ++propertyIt) {
for (auto strIt = propertyIt->second.begin(), strEnd = propertyIt->second.end(); strIt != strEnd; ++strIt) {
op(strIt);
}
}
}
return reply;
}

struct tokenize_strings {
void operator () (tstring_array::const_iterator& strIt) {
const auto tokens = StringProcessing::tokenize(*strIt);
values.push_back(tokens);
}

std::set<tstring> variableNames() const {
std::set<tstring> allVariableNames;
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
const auto variableNames = StringProcessing::extractVariableNames(*it);
allVariableNames.insert(variableNames.begin(), variableNames.end());
}

return allVariableNames;
}

std::vector<StringProcessing::TokenizedString> values;
};

class expand_macros {
public:
expand_macros(const CfgFile::Macros& m,
std::vector<StringProcessing::TokenizedString>::iterator iter) : macros(m), curTokenizedString(iter) {
}

void operator () (tstring_array::iterator& strIt) {
StringProcessing::expandVariables(*curTokenizedString, macros);
auto newStr = StringProcessing::stringify(*curTokenizedString);
++curTokenizedString;
if (*strIt != newStr) {
LOG_TRACE(tstrings::any() << "Map [" << *strIt << "] into [" << newStr << "]");
}
strIt->swap(newStr);
}

private:
const CfgFile::Macros& macros;
std::vector<StringProcessing::TokenizedString>::iterator curTokenizedString;
};

} // namespace

CfgFile CfgFile::expandMacros(const Macros& macros) const {
CfgFile copyCfgFile = *this;

PropertyMap::iterator mapIt = copyCfgFile.data.begin();
const PropertyMap::iterator mapEnd = copyCfgFile.data.end();
for (; mapIt != mapEnd; ++mapIt) {
Properties::iterator propertyIt = mapIt->second.begin();
const Properties::iterator propertyEnd = mapIt->second.end();
for (; propertyIt != propertyEnd; ++propertyIt) {
tstring_array::iterator strIt = propertyIt->second.begin();
const tstring_array::iterator strEnd = propertyIt->second.end();
for (; strIt != strEnd; ++strIt) {
tstring newValue;
while ((newValue = ::expandMacros(*strIt, macros)) != *strIt) {
strIt->swap(newValue);
}
tokenize_strings tokenizedStrings;
iterateProperties(static_cast<const CfgFile::PropertyMap&>(copyCfgFile.data), tokenizedStrings);

Macros allMacros(macros);

const auto variableNames = tokenizedStrings.variableNames();
for (auto it = variableNames.begin(), end = variableNames.end(); it != end; ++it) {
if (macros.find(*it) == macros.end()) {
// Not one of the reserved macro names. Assuming an environment variable.
const auto envVarName = *it;
if (SysInfo::isEnvVariableSet(envVarName)) {
const auto envVarValue = SysInfo::getEnvVariable(envVarName);
allMacros[envVarName] = envVarValue;
}
}
}

expand_macros expandMacros(allMacros, tokenizedStrings.values.begin());
iterateProperties(copyCfgFile.data, expandMacros);

return copyCfgFile;
}

Expand Down
9 changes: 4 additions & 5 deletions src/jdk.jpackage/share/native/applauncher/CfgFile.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -27,8 +27,7 @@
#ifndef CfgFile_h
#define CfgFile_h

#include <map>
#include "tstrings.h"
#include "StringProcessing.h"


class CfgFile {
Expand Down Expand Up @@ -90,10 +89,10 @@ class CfgFile {
std::swap(empty, other.empty);
}

typedef std::map<tstring, tstring> Macros;
typedef StringProcessing::VariableValues Macros;

/**
* Returns copy of this instance with the given macros expanded.
* Returns copy of this instance with the given macros and environment variables expanded.
*/
CfgFile expandMacros(const Macros& macros) const;

Expand Down
Loading