| // Licensed to the Apache Software Foundation (ASF) under one or more | |
| // contributor license agreements. See the NOTICE file distributed with | |
| // this work for additional information regarding copyright ownership. | |
| // The ASF licenses this file to You 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 fs_tool | |
| import ( | |
| "bytes" | |
| "context" | |
| "errors" | |
| "fmt" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| "github.com/google/uuid" | |
| pb "beam.apache.org/playground/backend/internal/api/v1" | |
| "beam.apache.org/playground/backend/internal/logger" | |
| "beam.apache.org/playground/backend/internal/utils" | |
| ) | |
| const ( | |
| JavaSourceFileExtension = ".java" | |
| javaCompiledFileExtension = ".class" | |
| javaEntryPointFullName = "public static void main(java.lang.String[])" | |
| javaDecompilerCommand = "javap" | |
| juintRunWithTestAnnotationConstant = "Lorg/junit/runner/RunWith;" | |
| ) | |
| // newJavaLifeCycle creates LifeCycle with java SDK environment. | |
| func newJavaLifeCycle(pipelineId uuid.UUID, pipelinesFolder string) *LifeCycle { | |
| javaLifeCycle := newCompilingLifeCycle(pipelineId, pipelinesFolder, JavaSourceFileExtension, javaCompiledFileExtension) | |
| javaLifeCycle.Paths.FindExecutableName = findExecutableName | |
| javaLifeCycle.Paths.FindTestExecutableName = findTestExecutableName | |
| return javaLifeCycle | |
| } | |
| // findExecutableName returns name of the .class file which has main() method | |
| func findExecutableName(ctx context.Context, executableFileFolderPath string) (string, error) { | |
| dirEntries, err := os.ReadDir(executableFileFolderPath) | |
| if err != nil { | |
| return "", err | |
| } | |
| if len(dirEntries) < 1 { | |
| return "", errors.New("number of executable files should be at least one") | |
| } | |
| if len(dirEntries) == 1 { | |
| return utils.TrimExtension(dirEntries[0].Name()), nil | |
| } | |
| for _, entry := range dirEntries { | |
| select { | |
| case <-ctx.Done(): | |
| return "", ctx.Err() | |
| default: | |
| filePath := fmt.Sprintf("%s/%s", executableFileFolderPath, entry.Name()) | |
| content, err := os.ReadFile(filePath) | |
| if err != nil { | |
| logger.Errorf("findExecutableName(): error when reading file %s: %s", entry.Name(), err.Error()) | |
| break | |
| } | |
| ext := filepath.Ext(entry.Name()) | |
| filename := strings.TrimSuffix(entry.Name(), ext) | |
| sdk := utils.ToSDKFromExt(ext) | |
| if sdk == pb.Sdk_SDK_UNSPECIFIED { | |
| logger.Errorf("findExecutableName(): file %s: unknown file extension: %s, skipping", entry.Name(), ext) | |
| continue | |
| } | |
| switch ext { | |
| case javaCompiledFileExtension: | |
| isMain, err := isMainClass(ctx, executableFileFolderPath, filename) | |
| if err != nil { | |
| logger.Errorf("findExecutableName(): file %s: error during checking main class: %s", entry.Name(), err.Error()) | |
| break | |
| } | |
| if isMain { | |
| logger.Infof("findExecutableName(): main file is %s", filename) | |
| return filename, nil | |
| } | |
| default: | |
| if utils.IsFileMain(string(content), sdk) { | |
| return filename, nil | |
| } | |
| } | |
| } | |
| } | |
| return "", errors.New("cannot find file with main() method") | |
| } | |
| // findTestExecutableName returns name of the .class file which has JUnit tests | |
| func findTestExecutableName(ctx context.Context, executableFileFolderPath string) (string, error) { | |
| dirEntries, err := os.ReadDir(executableFileFolderPath) | |
| if err != nil { | |
| return "", err | |
| } | |
| if len(dirEntries) < 1 { | |
| return "", errors.New("number of executable files should be at least one") | |
| } | |
| if len(dirEntries) == 1 { | |
| return utils.TrimExtension(dirEntries[0].Name()), nil | |
| } | |
| for _, entry := range dirEntries { | |
| select { | |
| case <-ctx.Done(): | |
| return "", ctx.Err() | |
| default: | |
| ext := filepath.Ext(entry.Name()) | |
| filename := strings.TrimSuffix(entry.Name(), ext) | |
| if ext == javaCompiledFileExtension { | |
| isTest, err := isTestClass(ctx, executableFileFolderPath, filename) | |
| if err != nil { | |
| logger.Errorf("findTestExecutableName(): file %s: error during checking main class: %s", entry.Name(), err.Error()) | |
| break | |
| } | |
| if isTest { | |
| logger.Infof("findTestExecutableName(): main file is %s", filename) | |
| return filename, nil | |
| } | |
| } | |
| } | |
| } | |
| return "", errors.New("cannot find file with unit tests") | |
| } | |
| func isMainClass(ctx context.Context, classPath string, className string) (bool, error) { | |
| cmd := exec.CommandContext(ctx, javaDecompilerCommand, "-public", "-classpath", classPath, className) | |
| var out bytes.Buffer | |
| cmd.Stdout = &out | |
| err := cmd.Run() | |
| if err != nil { | |
| return false, err | |
| } | |
| return strings.Contains(out.String(), javaEntryPointFullName), nil | |
| } | |
| func isTestClass(ctx context.Context, classPath string, className string) (bool, error) { | |
| cmd := exec.CommandContext(ctx, javaDecompilerCommand, "-verbose", "-classpath", classPath, className) | |
| var out bytes.Buffer | |
| cmd.Stdout = &out | |
| err := cmd.Run() | |
| if err != nil { | |
| return false, err | |
| } | |
| return strings.Contains(out.String(), juintRunWithTestAnnotationConstant), nil | |
| } |