我很确定这是一个错误,我已将其发送到SCons用户邮件列表中;在我等待修复时,我发帖要求可能的解决方法,或澄清这不是一个错误以及我应该如何处理它。
我有一个最小的案例来重现这个bug,但有2个文件,SConstruct和src / Hello / World / HelloWorld.java。
我在Windows 10上使用SCons 3.0.0和Python 2.7。
的src /你好/世界/ HelloWorld.java:
package Hello.World;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
SConstruct:
env = Environment( tools = ['javac'] )
# java
env.Append( JAVAVERSION = '1.8' )
env.Append( JAVA_HOME = 'C:/Program Files/Java/jdk1.8.0_152' )
env.AppendENVPath( 'PATH', 'C:/Program Files/Java/jdk1.8.0_152/bin' )
##### directories
src = Dir( 'src' )
build = Dir( 'build' )
classDir = build.Dir( 'classes' )
##### functions
srcJava = src.File( 'Hello/World/HelloWorld.java' )
copiedJava = build.File( 'Hello/World/HelloWorld.java' )
builtJava = env.Command( target = copiedJava, source = srcJava,
action = Copy( '$TARGET', '$SOURCE' ) )
classes = env.Java( target = classDir, source = builtJava )
使用运行Copy()的env.Command()将HelloWorld.java复制到另一个构建目录中,然后将复制的.java文件放入env.Java()中。构建工作正常,build / classes / Hello / World / HelloWorld.class被正确创建,但再次运行SCons会产生问题。
C:/example> scons --debug=explain -Q
scons: building `build\classes\HelloWorld.class' because it doesn't exist
javac -d build\classes -sourcepath build\Hello\World build\Hello\World\HelloWorld.java
SCons节点指向错误的位置。发生这种情况的原因是因为Javac发射器。
在SCons / Tool / javac.py的第96/97行:
if not f.is_derived():
pkg_dir, classes = parse_java_file(f.rfile().get_abspath(), version)
这两行基本上意味着只有在考虑源文件“未派生”的情况下,java文件才会被解析为import语句。'我在我的例子中通过复制文件来模拟这个,但如果你在项目中有任何生成的.java文件,那么在实践中这是一个很大的问题。
至于迄今为止我已经研究过的变通方法,尝试访问文件的is_derived()属性非常困难,有很多只读函数可以检查节点是否有构建器。 Java构建器似乎不会将.class文件作为目标参数,因此我无法通过更明确地修复它。
还有其他人遇到过这个问题吗?有没有我想过的聪明的解决方法?现在,我只是将Tool / javac.py中的if语句改为'如果为True:'直到我可以从项目的这一部分中删除包结构。
---更新---
正如bdbaddog所指出的,我的“如果是的话:'修复没有工作;一旦生成的文件消失,SCons将尝试在构建之前解析该文件。错误随之而来。
从那时起,我创建了自己的Tool / javac.py版本,我称之为JavaFix.py,它通过略微改进的发射器重置Java()构建器。大多数文件是相同的,所以我只是发布更新的发射器和辅助函数:
def findComDir(entry):
"""Return a node representing the base of the source tree.
Assumes that the first module of the package is named 'com.'
"""
node = entry
while node.name != 'com':
node = node.dir
if ':' in node.name:
return entry
return node
def emit_java_classes(target, source, env):
"""Create and return lists of source java files
and their corresponding target class files.
"""
java_suffix = env.get('JAVASUFFIX', '.java')
class_suffix = env.get('JAVACLASSSUFFIX', '.class')
target[0].must_be_same(SCons.Node.FS.Dir)
classdir = target[0]
s = source[0].rentry().disambiguate()
if isinstance(s, SCons.Node.FS.File):
comDir = findComDir(s)
sourcedir = comDir.dir.rdir()
elif isinstance(s, SCons.Node.FS.Dir):
sourcedir = s.rdir()
else:
raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % s.__class__)
slist = []
js = _my_normcase(java_suffix)
for entry in source:
entry = entry.rentry().disambiguate()
if isinstance(entry, SCons.Node.FS.File):
slist.append(entry)
elif isinstance(entry, SCons.Node.FS.Dir):
result = SCons.Util.OrderedDict()
dirnode = entry.rdir()
def find_java_files(arg, dirpath, filenames):
java_files = sorted([n for n in filenames
if _my_normcase(n).endswith(js)])
mydir = dirnode.Dir(dirpath)
java_paths = [mydir.File(f) for f in java_files]
for jp in java_paths:
arg[jp] = True
for dirpath, dirnames, filenames in os.walk(dirnode.get_abspath()):
find_java_files(result, dirpath, filenames)
entry.walk(find_java_files, result)
slist.extend(list(result.keys()))
else:
raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % entry.__class__)
version = env.get('JAVAVERSION', '1.4')
full_tlist = []
for f in slist:
tlist = []
source_file_based = True
pkg_dir = None
if not f.is_derived():
pkg_dir, classes = parse_java_file(f.rfile().get_abspath(), version)
if classes:
source_file_based = False
if pkg_dir:
d = target[0].Dir(pkg_dir)
p = pkg_dir + os.sep
else:
d = target[0]
p = ''
for c in classes:
t = d.File(c + class_suffix)
t.attributes.java_classdir = classdir
t.attributes.java_sourcedir = sourcedir
t.attributes.java_classname = classname(p + c)
tlist.append(t)
if source_file_based:
base = f.name[:-len(java_suffix)]
if pkg_dir:
t = target[0].Dir(pkg_dir).File(base + class_suffix)
else:
pkg_dir = sourcedir.rel_path( f.dir ).replace('.', os.sep)
if pkg_dir == os.sep:
pkg_dir = None
if pkg_dir:
p = pkg_dir + os.sep
else:
p = ''
t = target[0].File(p + base + class_suffix)
t.attributes.java_classdir = classdir
t.attributes.java_sourcedir = sourcedir
t.attributes.java_classname = classname(p + base)
tlist.append(t)
for t in tlist:
t.set_specific_source([f])
full_tlist.extend(tlist)
return full_tlist, slist
主要变化发生在if source_file_based:
行之后。这只是目前的初稿,但它确实有效。我将发布我放在JavaFix.py顶部的注释来解释:
The original Tools/javac.py couldn't really handle generated
.java files, so this slightly enhanced copy provides a fix.
Simply import the module and call JavaFix.generate(env),
and the Java builder will be remade with an improved emitter
that is better at dealing with generated files.
--Improvements--
When calling env.Java( target, source ), source can now handle
generated files.
--If source is a File node, the output directory structure is
deduced by copying the directory structure source is in,
starting from 'com.' If 'com' is not in the directory path,
SCons will not correctly locate the output node
--If source is a Dir node, SCons will correctly build all .java
files in the directory tree so long as source is the directory
containing the start of the package structure
(e.g. source contains com, and the package structure is com.Foo)
--Functionality concerning non-generated .java files is preserved
还有一些缺点; SCons不会接受生成的内部类(带有$的.class文件),并且它在很大程度上依赖于目录结构来确定输出文件的位置,但至少它没有得到困惑并试图重建它并不是一直需要的文件。
我希望这有助于bdbaddog或任何在SCons工作的人将修复此错误。我承认我的一些代码有点混乱,但这应该是可以理解的。
马修