diff --git a/ui/build/Android.bp b/ui/build/Android.bp index d44c112d0..7a83684cb 100644 --- a/ui/build/Android.bp +++ b/ui/build/Android.bp @@ -25,6 +25,7 @@ bootstrap_go_package { "context.go", "environment.go", "exec.go", + "java.go", "kati.go", "make.go", "ninja.go", diff --git a/ui/build/build.go b/ui/build/build.go index 67850829a..b84dd7d7b 100644 --- a/ui/build/build.go +++ b/ui/build/build.go @@ -15,6 +15,7 @@ package build import ( + "io/ioutil" "os" "path/filepath" "text/template" @@ -59,6 +60,37 @@ const ( BuildAll = BuildProductConfig | BuildSoong | BuildKati | BuildNinja ) +func checkCaseSensitivity(ctx Context, config Config) { + outDir := config.OutDir() + lowerCase := filepath.Join(outDir, "casecheck.txt") + upperCase := filepath.Join(outDir, "CaseCheck.txt") + lowerData := "a" + upperData := "B" + + err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777) + if err != nil { + ctx.Fatalln("Failed to check case sensitivity:", err) + } + + err = ioutil.WriteFile(upperCase, []byte(upperData), 0777) + if err != nil { + ctx.Fatalln("Failed to check case sensitivity:", err) + } + + res, err := ioutil.ReadFile(lowerCase) + if err != nil { + ctx.Fatalln("Failed to check case sensitivity:", err) + } + + if string(res) != lowerData { + ctx.Println("************************************************************") + ctx.Println("You are building on a case-insensitive filesystem.") + ctx.Println("Please move your source tree to a case-sensitive filesystem.") + ctx.Println("************************************************************") + ctx.Fatalln("Case-insensitive filesystems not supported") + } +} + // Build the tree. The 'what' argument can be used to chose which components of // the build to run. func Build(ctx Context, config Config, what int) { @@ -86,8 +118,13 @@ func Build(ctx Context, config Config, what int) { return } + // Start getting java version as early as possible + getJavaVersions(ctx, config) + SetupOutDir(ctx, config) + checkCaseSensitivity(ctx, config) + if what&BuildProductConfig != 0 { // Run make for product config runMakeProductConfig(ctx, config) @@ -99,6 +136,9 @@ func Build(ctx Context, config Config, what int) { runSoong(ctx, config) } + // Check the java versions we read earlier + checkJavaVersion(ctx, config) + if what&BuildKati != 0 { // Run ckati runKati(ctx, config) diff --git a/ui/build/config.go b/ui/build/config.go index e677d9301..0d2992437 100644 --- a/ui/build/config.go +++ b/ui/build/config.go @@ -106,6 +106,32 @@ func NewConfig(ctx Context, args ...string) Config { log.Fatalln("Error verifying tree state:", err) } + if srcDir, err := filepath.Abs("."); err == nil { + if strings.ContainsRune(srcDir, ' ') { + log.Println("You are building in a directory whose absolute path contains a space character:") + log.Println() + log.Printf("%q\n", srcDir) + log.Println() + log.Fatalln("Directory names containing spaces are not supported") + } + } + + if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') { + log.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:") + log.Println() + log.Printf("%q\n", outDir) + log.Println() + log.Fatalln("Directory names containing spaces are not supported") + } + + if distDir := ret.DistDir(); strings.ContainsRune(distDir, ' ') { + log.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:") + log.Println() + log.Printf("%q\n", distDir) + log.Println() + log.Fatalln("Directory names containing spaces are not supported") + } + for _, arg := range args { arg = strings.TrimSpace(arg) if arg == "--make-mode" { diff --git a/ui/build/exec.go b/ui/build/exec.go index 4c45c507d..c8c5c9a57 100644 --- a/ui/build/exec.go +++ b/ui/build/exec.go @@ -84,24 +84,38 @@ func (c *Cmd) StartOrFatal() { } } +func (c *Cmd) reportError(err error) { + if err == nil { + return + } + if e, ok := err.(*exec.ExitError); ok { + c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String()) + } else { + c.ctx.Fatalf("Failed to run %s: %v", c.name, err) + } +} + // RunOrFatal is equivalent to Run, but handles the error with a call to ctx.Fatal func (c *Cmd) RunOrFatal() { - if err := c.Run(); err != nil { - if e, ok := err.(*exec.ExitError); ok { - c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String()) - } else { - c.ctx.Fatalf("Failed to run %s: %v", c.name, err) - } - } + c.reportError(c.Run()) } // WaitOrFatal is equivalent to Wait, but handles the error with a call to ctx.Fatal func (c *Cmd) WaitOrFatal() { - if err := c.Wait(); err != nil { - if e, ok := err.(*exec.ExitError); ok { - c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String()) - } else { - c.ctx.Fatalf("Failed to run %s: %v", c.name, err) - } - } + c.reportError(c.Wait()) +} + +// OutputOrFatal is equivalent to Output, but handles the error with a call to ctx.Fatal +func (c *Cmd) OutputOrFatal() []byte { + ret, err := c.Output() + c.reportError(err) + return ret +} + +// CombinedOutputOrFatal is equivalent to CombinedOutput, but handles the error with +// a call to ctx.Fatal +func (c *Cmd) CombinedOutputOrFatal() []byte { + ret, err := c.CombinedOutput() + c.reportError(err) + return ret } diff --git a/ui/build/java.go b/ui/build/java.go new file mode 100644 index 000000000..5a09b1a29 --- /dev/null +++ b/ui/build/java.go @@ -0,0 +1,156 @@ +// Copyright 2017 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package build + +import ( + "regexp" + "runtime" + "strings" + "sync" +) + +const incompatibleJavacStr = "google" + +var javaVersionInfo = struct { + once sync.Once + startOnce sync.Once + + java_version_output string + javac_version_output string +}{} + +func getJavaVersions(ctx Context, config Config) { + javaVersionInfo.startOnce.Do(func() { + go func() { + if ctx.Tracer != nil { + thread := ctx.Tracer.NewThread("java_version") + ctx.Tracer.Begin("get version", thread) + defer ctx.Tracer.End(thread) + } + + getJavaVersionsImpl(ctx, config) + }() + }) +} + +func getJavaVersionsImpl(ctx Context, config Config) { + javaVersionInfo.once.Do(func() { + cmd := Command(ctx, config, "java", "java", "-version") + cmd.Environment.Unset("_JAVA_OPTIONS") + javaVersionInfo.java_version_output = string(cmd.CombinedOutputOrFatal()) + + cmd = Command(ctx, config, "javac", "javac", "-version") + cmd.Environment.Unset("_JAVA_OPTIONS") + javaVersionInfo.javac_version_output = string(cmd.CombinedOutputOrFatal()) + }) +} + +func checkJavaVersion(ctx Context, config Config) { + ctx.BeginTrace("java_version_check") + defer ctx.EndTrace() + + getJavaVersionsImpl(ctx, config) + + var required_java_version string + var java_version_regexp *regexp.Regexp + var javac_version_regexp *regexp.Regexp + if legacy, _ := config.Environment().Get("LEGACY_USE_JAVA7"); legacy != "" { + required_java_version = "1.7" + java_version_regexp = regexp.MustCompile(`^java .*[ "]1\.7[\. "$]`) + javac_version_regexp = regexp.MustCompile(`[ "]1\.7[\. "$]`) + } else { + required_java_version = "1.8" + java_version_regexp = regexp.MustCompile(`[ "]1\.8[\. "$]`) + javac_version_regexp = java_version_regexp + } + + java_version := javaVersionInfo.java_version_output + javac_version := javaVersionInfo.javac_version_output + + found := false + for _, l := range strings.Split(java_version, "\n") { + if java_version_regexp.MatchString(l) { + java_version = l + found = true + break + } + } + if !found { + ctx.Println("***************************************************************") + ctx.Println("You are attempting to build with the incorrect version of java.") + ctx.Println() + ctx.Println("Your version is:", java_version) + ctx.Println("The required version is:", required_java_version+".x") + ctx.Println() + ctx.Println("Please follow the machine setup instructions at:") + ctx.Println(" https://source.android.com/source/initializing.html") + ctx.Println("***************************************************************") + ctx.Fatalln("stop") + } + + if runtime.GOOS == "linux" { + if !strings.Contains(java_version, "openjdk") { + ctx.Println("*******************************************************") + ctx.Println("You are attempting to build with an unsupported JDK.") + ctx.Println() + ctx.Println("Only an OpenJDK based JDK is supported.") + ctx.Println() + ctx.Println("Please follow the machine setup instructions at:") + ctx.Println(" https://source.android.com/source/initializing.html") + ctx.Println("*******************************************************") + ctx.Fatalln("stop") + } + } else { // darwin + if strings.Contains(java_version, "openjdk") { + ctx.Println("*******************************************************") + ctx.Println("You are attempting to build with an unsupported JDK.") + ctx.Println() + ctx.Println("You use OpenJDK, but only Sun/Oracle JDK is supported.") + ctx.Println() + ctx.Println("Please follow the machine setup instructions at:") + ctx.Println(" https://source.android.com/source/initializing.html") + ctx.Println("*******************************************************") + ctx.Fatalln("stop") + } + } + + incompatible_javac := strings.Contains(javac_version, incompatibleJavacStr) + + found = false + for _, l := range strings.Split(javac_version, "\n") { + if javac_version_regexp.MatchString(l) { + javac_version = l + found = true + break + } + } + if !found || incompatible_javac { + ctx.Println("****************************************************************") + ctx.Println("You are attempting to build with the incorrect version of javac.") + ctx.Println() + ctx.Println("Your version is:", javac_version) + if incompatible_javac { + ctx.Println("The '" + incompatibleJavacStr + "' version is not supported for Android platform builds.") + ctx.Println("Use a publically available JDK and make sure you have run envsetup.sh / lunch.") + } else { + ctx.Println("The required version is:", required_java_version) + } + ctx.Println() + ctx.Println("Please follow the machine setup instructions at:") + ctx.Println(" https://source.android.com/source/initializing.html") + ctx.Println("****************************************************************") + ctx.Fatalln("stop") + } +} diff --git a/ui/tracer/tracer.go b/ui/tracer/tracer.go index b37288568..f19ac186f 100644 --- a/ui/tracer/tracer.go +++ b/ui/tracer/tracer.go @@ -46,6 +46,8 @@ type Tracer interface { Complete(name string, thread Thread, begin, end uint64) ImportNinjaLog(thread Thread, filename string, startOffset time.Time) + + NewThread(name string) Thread } type tracerImpl struct {