├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── SECURITY.md ├── build.sh ├── cmd ├── check.go ├── clean.go ├── create.go ├── list.go ├── root.go └── version.go ├── generate-windowsdata.sh ├── go.mod ├── go.sum ├── main.go ├── pkg ├── aggregator │ ├── aggregator.go │ ├── aggregator_test.go │ ├── download.go │ ├── file.go │ └── types.go ├── browser │ ├── browser_darwin.go │ ├── browser_linux.go │ └── browser_windows.go ├── deps │ ├── checker.go │ ├── checker_test.go │ └── constants.go ├── extractor │ ├── tar.go │ ├── tar_test.go │ └── testdata │ │ ├── README.md │ │ ├── golden.tar.gz │ │ └── ok.tar.gz └── ui │ ├── cli.go │ ├── cli_test.go │ └── testdata │ ├── cpp.json │ ├── python.json │ └── zoo │ ├── cpp.tar.gz │ └── python.tar.gz ├── test.sh ├── windowsdata.syso └── winres.rc /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /build/ 3 | /config.* 4 | !config.toml.dist 5 | /docker-compose.override.yml 6 | /var/ 7 | /vendor/ 8 | cover.out 9 | 10 | # IDE integration 11 | /.vscode/* 12 | !/.vscode/launch.json 13 | !/.vscode/tasks.json 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}", 13 | "env": {}, 14 | "args": [""] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This license file applies to the oneapi-cli application. 2 | 3 | If you wish to obtain access to the source for the above-licensed component, 4 | please visit https://github.com/intel/vscode-sample-browser. 5 | 6 | SPDX-License-Identifier: BSD-3-Clause 7 | https://opensource.org/licenses/BSD-3-Clause 8 | 9 | _____________________________________________________________________________ 10 | 11 | BSD 3-Clause "New" or "Revised" License 12 | 13 | Copyright (C) 2019, Intel Corporation. All rights reserved. 14 | 15 | 16 | Redistribution and use in source and binary forms, with or without modification, 17 | are permitted provided that the following conditions are met: 18 | 19 | - Redistributions of source code must retain the above copyright notice, 20 | this list of conditions and the following disclaimer. 21 | 22 | - Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | - Neither the name of the copyright holder nor the names of its contributors may 27 | be used to endorse or promote products derived from this software without 28 | specific prior written permission. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 31 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 32 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 33 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 34 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 35 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 36 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 37 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oneapi-cli tool 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/intel/oneapi-cli)](https://goreportcard.com/report/github.com/intel/oneapi-cli) 3 | 4 | `oneapi-cli` is a tool to help you get started with Intel® oneAPI 5 | 6 | ## Where to find Intel® oneAPI. 7 | 8 | This tool does not provide any of the tools that may be required to compile/run the samples `oneapi-cli` can extract for you. 9 | 10 | Please visit https://software.intel.com/en-us/oneapi for details. 11 | 12 | ## Development Install 13 | 14 | Fetch using 15 | ```bash 16 | go get github.com/intel/oneapi-cli 17 | ``` 18 | Alternatively see the tags/releases for a binary build for your OS. 19 | 20 | ## Building 21 | Go 1.20 should be used to build the CLI/TUI app. 22 | 23 | ```bash 24 | git clone https://github.com/intel/oneapi-cli.git 25 | cd oneapi-cli 26 | go build 27 | ./oneapi-cli 28 | ``` 29 | 30 | There is also a `build.sh` which will embed version information within the build. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project [utilizing the guidelines here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export CGO_ENABLED=0 3 | 4 | version=$(git describe --long --dirty --abbrev=10 --tags) 5 | lf="-X github.com/intel/oneapi-cli/cmd.version=${version}" 6 | 7 | GOOS=linux GOARCH=amd64 go build -trimpath -mod=readonly -gcflags="all=-spectre=all -N -l" -asmflags="all=-spectre=all" -ldflags="all=-s -w $lf" -o linux/bin/oneapi-cli 8 | GOOS=windows GOARCH=amd64 go build -trimpath -mod=readonly -gcflags="all=-spectre=all -N -l" -asmflags="all=-spectre=all" -ldflags="all=-s -w $lf" -o win/bin/oneapi-cli.exe 9 | GOOS=darwin GOARCH=amd64 go build -trimpath -mod=readonly -gcflags="all=-spectre=all -N -l" -asmflags="all=-spectre=all" -ldflags="all=-s -w $lf" -o osx/bin/oneapi-cli -------------------------------------------------------------------------------- /cmd/check.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/intel/oneapi-cli/pkg/deps" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var depsParam []string 15 | var root string 16 | 17 | // checkCmd represents the check command 18 | var checkCmd = &cobra.Command{ 19 | Use: "check", 20 | Short: "check dependencies", 21 | Hidden: true, 22 | Long: `check dependencies, returns an error/retrieve-it- message if dependencies are absent`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | 25 | if root == "" { 26 | //Find the oneAPI root 27 | var err error 28 | root, err = deps.GetOneAPIRoot() 29 | if err != nil { 30 | fmt.Println(err) //Failed to find the Env, may be unset. 31 | os.Exit(-1) 32 | } 33 | 34 | } 35 | 36 | //Check the deps at the found root. 37 | msg, errCode := deps.CheckDeps(depsParam, root) 38 | if errCode != 0 { 39 | fmt.Println(msg) 40 | os.Exit(errCode) 41 | } 42 | 43 | }, 44 | } 45 | 46 | func init() { 47 | rootCmd.AddCommand(checkCmd) 48 | checkCmd.Flags().StringSliceVarP(&depsParam, "deps", "", nil, "comma seperated dependency array") 49 | checkCmd.Flags().StringVar(&root, "oneapi-root", "", "(optional) path to oneAPI root, default attempts to use environment varible ONEAPI_ROOT") 50 | } 51 | -------------------------------------------------------------------------------- /cmd/clean.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/intel/oneapi-cli/pkg/aggregator" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | // cleanCmd represents the clean command 16 | var cleanCmd = &cobra.Command{ 17 | Use: "clean", 18 | Short: "Clean Sample Cache", 19 | Long: `Removes local Sample Cache`, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | if err := os.RemoveAll(filepath.Join(baseFilePath, aggregator.AggregatorLocalAPILevel)); err != nil { 22 | fmt.Println("Failed to clean sample cache.") 23 | fmt.Printf("%s \n", err) 24 | os.Exit(1) 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | rootCmd.AddCommand(cleanCmd) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/intel/oneapi-cli/pkg/aggregator" 11 | "github.com/intel/oneapi-cli/pkg/extractor" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var sampleLang string 16 | 17 | // listCmd represents the list command 18 | var createCmd = &cobra.Command{ 19 | Use: "create", 20 | Short: "Create Sample", 21 | Hidden: true, 22 | Long: `Creates the sample based on the passed in path 23 | 24 | i.e. oneapi-cli create -s cpp my/long/path/from/index/json /tmp/mynewproject`, 25 | Run: func(cmd *cobra.Command, args []string) { 26 | 27 | //Arg 0 being sample 28 | //arg 1 being where to create the sample. Complete path 29 | 30 | if len(args) != 2 || args[0] == "" || args[1] == "" { 31 | fmt.Println("Please pass both a sample and where you want it extracted to") 32 | os.Exit(1) 33 | } 34 | 35 | tarPath, err := aggregator.GetTarBall(baseFilePath, baseURL, sampleLang, args[0]) 36 | if err != nil { 37 | fmt.Println(err) 38 | os.Exit(2) 39 | } 40 | err = extractor.ExtractTarGz(tarPath, args[1]) 41 | if err != nil { 42 | fmt.Println(err) 43 | os.Exit(3) 44 | } 45 | 46 | }, 47 | } 48 | 49 | func init() { 50 | rootCmd.AddCommand(createCmd) 51 | createCmd.Flags().StringVarP(&sampleLang, "sampleLangauge", "s", "cpp", "specific language of the samples you want to create") 52 | } 53 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func prettyPrint(i interface{}) string { 15 | s, err := json.MarshalIndent(i, "", "\t") 16 | if err != nil { 17 | fmt.Println("Failed to pretty print Json") 18 | fmt.Printf("%s \n", err) 19 | os.Exit(1) 20 | } 21 | return string(s) 22 | } 23 | 24 | var language string 25 | var outputJSON bool 26 | 27 | // listCmd represents the list command 28 | var listCmd = &cobra.Command{ 29 | Use: "list", 30 | Short: "List Samples", 31 | Long: `Lists the available samples. Checks online if newer sample index 32 | is available`, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | 35 | if language == "" { 36 | for _, l := range getAggregator().GetLanguages() { 37 | fmt.Printf("%s\n", l) 38 | } 39 | os.Exit(1) 40 | } 41 | 42 | //Check language is part of the support ones from the aggregator module 43 | var found bool 44 | for _, l := range getAggregator().GetLanguages() { 45 | if l == language { 46 | found = true 47 | break 48 | } 49 | } 50 | if !found { 51 | fmt.Printf("Invalid language provided, available languages: %v\n", getAggregator().GetLanguages()) 52 | os.Exit(1) 53 | } 54 | 55 | if getAggregator().Samples[language] == nil { 56 | if outputJSON { 57 | fmt.Printf("[]") 58 | } 59 | return 60 | } 61 | 62 | if outputJSON { 63 | fmt.Printf("%s\n", prettyPrint(getAggregator().Samples[language])) 64 | return 65 | } 66 | 67 | for _, s := range getAggregator().Samples[language] { 68 | fmt.Printf("%s:\n\t%s\n", s.Fields.Name, s.Fields.Description) 69 | } 70 | }, 71 | } 72 | 73 | func init() { 74 | rootCmd.AddCommand(listCmd) 75 | listCmd.Flags().StringVarP(&language, "output", "o", "", "specific language samples you want to list") 76 | listCmd.Flags().BoolVarP(&outputJSON, "json", "j", false, "output as JSON") 77 | } 78 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/intel/oneapi-cli/pkg/aggregator" 14 | "github.com/intel/oneapi-cli/pkg/ui" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | const SamplesEndpointDefault = "https://iotdk.intel.com/samples-iss" 19 | 20 | const SampleLatestKey = "latest" 21 | 22 | const LocalStorageDefault = ".oneapi-cli" 23 | 24 | var baseURL string 25 | var baseFilePath string 26 | var cAggregator *aggregator.Aggregator 27 | var defaultLanguages = []string{"cpp", "python", "fortran"} 28 | var enabledLanguages []string 29 | var userHome string 30 | var ignoreOS bool 31 | var bulk bool 32 | 33 | // rootCmd represents the base command when called without any subcommands 34 | var rootCmd = &cobra.Command{ 35 | Use: "oneapi-cli", 36 | Short: "oneapi-cli a tool to fetch samples", 37 | Long: `oneapi-cli is tool for fetching samples. It intends to be used either 38 | interactively or called from another tool`, 39 | 40 | Run: func(cmd *cobra.Command, args []string) { 41 | fmt.Printf("Connecting to online Sample Aggregator, this may take some time based on network conditions\n") 42 | app, err := ui.NewCLI(getAggregator(), userHome) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | app.Show() 47 | 48 | }, 49 | } 50 | 51 | func getAggregator() *aggregator.Aggregator { 52 | if cAggregator == nil { 53 | var err error 54 | cAggregator, err = aggregator.NewAggregator(baseURL, baseFilePath, enabledLanguages, ignoreOS, bulk) 55 | if err != nil && err != aggregator.ErrCacheLock { 56 | //Most errors we are going to find are network related :/ 57 | fmt.Printf("Failed to fetch sample index, this *may* be your network/proxy environment.\nYou might try setting http_proxy in your environment, for example:\n") 58 | fmt.Printf("\tLinux/Mac: export http_proxy=http://your.proxy:8080\n") 59 | fmt.Printf("\tWindows: set http_proxy=http://your.proxy:8080\n") 60 | fmt.Printf("%v\n", err) 61 | os.Exit(1) 62 | } 63 | if err == aggregator.ErrCacheLock { 64 | fmt.Printf("Local Sample cache is corrupt! Please clean the cache and retry!\n") 65 | fmt.Printf("\toneapi-cli clean\n") 66 | fmt.Printf("\toneapi-cli\n") 67 | os.Exit(1) 68 | } 69 | } 70 | return cAggregator 71 | 72 | } 73 | 74 | // Execute adds all child commands to the root command and sets flags appropriately. 75 | // This is called by main.main(). It only needs to happen once to the rootCmd. 76 | func Execute() { 77 | if err := rootCmd.Execute(); err != nil { 78 | fmt.Println(err) 79 | os.Exit(1) 80 | } 81 | } 82 | 83 | func init() { 84 | 85 | var err error 86 | userHome, err = os.UserHomeDir() 87 | if err != nil { 88 | fmt.Printf("Unable to locate Home Directory - %v\n", err) 89 | os.Exit(1) 90 | } 91 | defaultBaseFilePath := filepath.Join(userHome, LocalStorageDefault) 92 | 93 | rootCmd.PersistentFlags().StringVarP(&baseURL, "url", "u", getVersionInfo(), "URL of remote sample aggregator") 94 | rootCmd.PersistentFlags().StringVarP(&baseFilePath, "directory", "d", defaultBaseFilePath, "location to store local oneapi samples cache") 95 | rootCmd.PersistentFlags().StringSliceVarP(&enabledLanguages, "languages", "l", defaultLanguages, "enabled languages") 96 | rootCmd.PersistentFlags().BoolVar(&ignoreOS, "ignore-os", false, "ignore Host-OS based filtering when showing/outputting samples") 97 | rootCmd.PersistentFlags().BoolVar(&bulk, "full-sync", false, "download all samples at startup") 98 | 99 | } 100 | 101 | // looks at the command bin path and looks for "samples-version-tag.txt" which 102 | 103 | // points to which sample version to look at. If it cant find it it 104 | // returns the Latestkey const 105 | func getVersionInfo() string { 106 | bin, err := os.Executable() 107 | if err != nil { 108 | return fmt.Sprintf("%s/%s/", SamplesEndpointDefault, SampleLatestKey) 109 | } 110 | bin, err = filepath.EvalSymlinks(bin) 111 | if err != nil { 112 | return fmt.Sprintf("%s/%s/", SamplesEndpointDefault, SampleLatestKey) 113 | } 114 | versionPath := filepath.Join(filepath.Dir(filepath.Dir(bin)), "etc", "samples-version-tag.txt") 115 | file, err := os.Open(versionPath) 116 | if err != nil { 117 | return fmt.Sprintf("%s/%s/", SamplesEndpointDefault, SampleLatestKey) 118 | } 119 | defer file.Close() 120 | 121 | scanner := bufio.NewScanner(file) 122 | scanner.Scan() 123 | if err := scanner.Err(); err != nil { 124 | return fmt.Sprintf("%s/%s/", SamplesEndpointDefault, SampleLatestKey) 125 | } 126 | return fmt.Sprintf("%s/%s/", SamplesEndpointDefault, scanner.Text()) 127 | } 128 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Version set during build via `go build -ldflags -X "-X main.Version=version"` 13 | var version string 14 | 15 | // versionCmd represents the version command 16 | var versionCmd = &cobra.Command{ 17 | Use: "version", 18 | Short: "Show the CLI version information", 19 | Long: `Show the CLI version information`, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | if version == "" { 22 | version = "devel" 23 | } 24 | fmt.Printf("%s\n", version) 25 | }, 26 | } 27 | 28 | func init() { 29 | rootCmd.AddCommand(versionCmd) 30 | } 31 | -------------------------------------------------------------------------------- /generate-windowsdata.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # How windres is named on your system may vary!! 4 | x86_64-w64-mingw32-windres -i winres.rc -O coff -o windowsdata.syso -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/oneapi-cli 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/gdamore/tcell v1.4.0 7 | github.com/mattn/go-ieproxy v0.0.1 8 | github.com/spf13/cobra v1.6.1 9 | gitlab.com/tslocum/cview v1.4.4 10 | // golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 11 | golang.org/x/sys v0.31.0 12 | ) 13 | 14 | require ( 15 | github.com/gdamore/encoding v1.0.0 // indirect 16 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 17 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 18 | github.com/mattn/go-runewidth v0.0.8 // indirect 19 | github.com/rivo/uniseg v0.4.2 // indirect 20 | github.com/spf13/pflag v1.0.5 // indirect 21 | golang.org/x/net v0.38.0 // indirect 22 | golang.org/x/text v0.23.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 3 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 4 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 5 | github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= 6 | github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= 7 | github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= 8 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 9 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 10 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 11 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 12 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 13 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 14 | github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= 15 | github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= 16 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 17 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 18 | github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= 19 | github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 20 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 21 | github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= 22 | github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 23 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 24 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 25 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 26 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 27 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 28 | gitlab.com/tslocum/cview v1.4.4 h1:sh1MUSN5zFd7vK+lHEq1jAxRD82TJb6uFW+EnECsEyc= 29 | gitlab.com/tslocum/cview v1.4.4/go.mod h1:+bEf1cg6IoWvL16YHJAKwGGpQf5s/nxXAA7YJr+WOHE= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 32 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 33 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 39 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 40 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 41 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 42 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 43 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 44 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 45 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package main 5 | 6 | import ( 7 | cmd "github.com/intel/oneapi-cli/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd.Execute() 12 | } 13 | -------------------------------------------------------------------------------- /pkg/aggregator/aggregator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package aggregator 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "net/url" 14 | "os" 15 | "path/filepath" 16 | "runtime" 17 | "strings" 18 | "sync" 19 | ) 20 | 21 | type sampleWorkItem struct { 22 | language string 23 | s Sample 24 | retry int 25 | } 26 | 27 | //Aggregator struct representing the sample store. Not really thread safe 28 | type Aggregator struct { 29 | baseURL *url.URL 30 | localPath string 31 | languages []string 32 | jobs chan sampleWorkItem 33 | results chan error 34 | wg sync.WaitGroup 35 | sampleCount sync.WaitGroup 36 | Samples Samples 37 | Online bool 38 | ignoreOS bool 39 | Bulk bool 40 | } 41 | 42 | const defaultRetry = 3 43 | 44 | //Samples a map containing an array of avaible samples for that language 45 | // i.e. Samples['cpp"] 46 | type Samples map[string][]Sample 47 | 48 | //AggregatorLocalAPILevel the current level of the local file cache. use BaseDir plus this 49 | const AggregatorLocalAPILevel = "v1" 50 | 51 | const cacheLockName = "lock" 52 | 53 | //ErrCacheLock Is thrown when aggregator's local cache is locked. 54 | var ErrCacheLock = errors.New("aggregator cache is locked") 55 | 56 | //HTTPTimeout timeout in seconds for HTTP operations 57 | const HTTPTimeout = 10 58 | 59 | //NewAggregator Gives you a Aggregator. 60 | func NewAggregator(URL string, FilePath string, languages []string, ignoreOS bool, bulk bool) (*Aggregator, error) { 61 | var a Aggregator 62 | if URL == "" { 63 | return nil, fmt.Errorf("no sample url passed") 64 | } 65 | u, err := checkURL(URL) 66 | if err != nil { 67 | return nil, err 68 | } 69 | a.baseURL = u 70 | 71 | if FilePath == "" { 72 | return nil, fmt.Errorf("no base directory passed") 73 | } 74 | 75 | a.ignoreOS = ignoreOS 76 | a.Bulk = bulk 77 | 78 | //Add Current file APP level 79 | a.localPath = filepath.Join(FilePath, AggregatorLocalAPILevel) 80 | 81 | a.languages = languages 82 | //Create Directory for local path 83 | if err := os.MkdirAll(a.localPath, 0750); err != nil { 84 | return nil, err //Package Tests do not cover this 85 | } 86 | 87 | if a.isLocked() { 88 | return nil, ErrCacheLock 89 | } 90 | 91 | if languages == nil || len(languages) < 1 { 92 | return nil, fmt.Errorf("No Languages are being selected") 93 | } 94 | 95 | a.Samples = make(map[string][]Sample) 96 | 97 | if err := a.Update(); err != nil { 98 | return nil, err 99 | } 100 | 101 | return &a, nil 102 | } 103 | 104 | func (a *Aggregator) isLocked() bool { 105 | return FileExists(filepath.Join(a.localPath, cacheLockName)) 106 | } 107 | 108 | func (a *Aggregator) lock() { 109 | _, err := os.Create(filepath.Join(a.localPath, cacheLockName)) 110 | if err != nil { 111 | log.Fatalf("failed to create cache lock file - %v", err) 112 | } 113 | } 114 | 115 | func sampleWorker(a *Aggregator, jobs <-chan sampleWorkItem, results chan<- error, wg *sync.WaitGroup) { 116 | defer wg.Done() 117 | for j := range jobs { 118 | j.retry-- 119 | err := a.workSample(j) // Try sync the sample 120 | if err == nil { 121 | a.sampleCount.Done() 122 | continue // Sync was good, move on 123 | } 124 | if j.retry > 0 { 125 | a.jobs <- j //Resumbit if retry is 126 | continue 127 | } 128 | a.sampleCount.Done() 129 | results <- err // Ran out of retries, submit failure to results 130 | } 131 | } 132 | 133 | func (a *Aggregator) workSample(w sampleWorkItem) error { 134 | _, err := GetTarBall(a.localPath, a.baseURL.String(), w.language, w.s.Path) 135 | if err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | 141 | func (a *Aggregator) setupWorkers(n int) { 142 | a.jobs = make(chan sampleWorkItem, 50) 143 | a.results = make(chan error, 100) 144 | 145 | for i := 0; i <= n; i++ { 146 | a.wg.Add(1) 147 | go sampleWorker(a, a.jobs, a.results, &a.wg) 148 | } 149 | } 150 | 151 | //syncLanguages interates over configured lanauges, and if a newer version is available online 152 | 153 | func (a *Aggregator) syncLanguagesIndex() error { 154 | var workingLanguages []string 155 | for _, language := range a.languages { 156 | localPath := filepath.Join(a.localPath, language+".json") 157 | update := false 158 | a.Online = true 159 | 160 | remoteHash, remote, indexErr := sha512URL(a.baseURL.String() + "/" + language + ".json") 161 | if indexErr != nil { 162 | log.Printf("failed to connect to sample aggregator for %s samples, attempting to use local cache\n", language) 163 | a.Online = false 164 | } 165 | if FileExists(localPath) { 166 | localHash, err := localHash(localPath) 167 | if err != nil { 168 | return err 169 | } 170 | if !bytes.Equal(remoteHash, localHash) { 171 | update = true 172 | 173 | } 174 | } else { 175 | if !a.Online { 176 | log.Printf("operating offline and local cache for %s samples does not exist\n\t%s\n", language, indexErr) 177 | continue 178 | } 179 | update = true 180 | } 181 | if update && a.Online { 182 | err := ioutil.WriteFile(localPath, remote, 0644) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | } 188 | //Ensure Directory for local path of language exists 189 | if err := os.MkdirAll(filepath.Join(a.localPath, language), 0750); err != nil { 190 | return err 191 | } 192 | workingLanguages = append(workingLanguages, language) 193 | } 194 | 195 | if len(workingLanguages) < 1 { 196 | return fmt.Errorf("no working sample languages configured %v", a.languages) 197 | } 198 | //Overwrite with known working languages. 199 | a.languages = workingLanguages 200 | 201 | return nil 202 | } 203 | 204 | //Update updates the local cache 205 | func (a *Aggregator) Update() error { 206 | err := a.syncLanguagesIndex() 207 | if err != nil { 208 | return err 209 | } 210 | 211 | var outerrors error 212 | 213 | var errWorker sync.WaitGroup 214 | if a.Bulk { 215 | //Setup threading pools for downloading all samples 216 | a.setupWorkers(5) //Start workerpool with 5 217 | 218 | //Start Error collection go routine. Will just return a generic error to 219 | //this function, will print real errors to fmt. for now 220 | errWorker.Add(1) 221 | go func() { 222 | for e := range a.results { 223 | if e != nil { 224 | fmt.Println(e) 225 | outerrors = fmt.Errorf("error occured on worker") 226 | } 227 | } 228 | if outerrors != nil { 229 | a.lock() //Poison the cache 230 | } 231 | errWorker.Done() 232 | }() 233 | } 234 | 235 | for _, language := range a.languages { 236 | localPath := filepath.Join(a.localPath, language+".json") 237 | if !FileExists(localPath) { 238 | return fmt.Errorf("unable to find configured language json (%s)", language) 239 | } 240 | languageIndex, err := ioutil.ReadFile(localPath) 241 | if err != nil { 242 | return err 243 | } 244 | var collected []Sample 245 | jsonErr := json.Unmarshal(languageIndex, &collected) 246 | if jsonErr != nil { 247 | return (jsonErr) 248 | } 249 | 250 | if !a.ignoreOS { 251 | collected = filterOnOS(collected) 252 | } 253 | 254 | if a.Bulk { 255 | for _, sample := range collected { 256 | a.sampleCount.Add(1) 257 | a.jobs <- sampleWorkItem{language, sample, defaultRetry} 258 | } 259 | } 260 | a.Samples[language] = collected 261 | 262 | } 263 | 264 | if a.Bulk { 265 | a.sampleCount.Wait() 266 | close(a.jobs) 267 | a.wg.Wait() //wait for job channel to be completed. 268 | close(a.results) //tell error channel workers are done. 269 | errWorker.Wait() //wait for the erros to be fully processed. 270 | } 271 | 272 | return outerrors 273 | 274 | } 275 | 276 | func filterOnOS(c []Sample) (filtered []Sample) { 277 | for _, s := range c { 278 | if len(s.Fields.OS) > 0 { 279 | keep := false 280 | for _, os := range s.Fields.OS { 281 | if strings.EqualFold(os, runtime.GOOS) { 282 | keep = true 283 | } 284 | } 285 | if keep { 286 | filtered = append(filtered, s) 287 | } 288 | } else { 289 | filtered = append(filtered, s) 290 | } 291 | } 292 | return filtered 293 | } 294 | 295 | //GetLocalPath returns the path local path 296 | func (a *Aggregator) GetLocalPath() string { 297 | return a.localPath 298 | } 299 | 300 | //GetURL gets the base URL used for fetching 301 | func (a *Aggregator) GetURL() string { 302 | return a.baseURL.String() 303 | } 304 | 305 | //GetLanguages gets the base URL used for fetching 306 | func (a *Aggregator) GetLanguages() []string { 307 | return a.languages 308 | } 309 | 310 | //GetTarBall Path of the tarball 311 | func GetTarBall(base string, baseURL string, language string, path string) (tar string, err error) { 312 | tarPath := filepath.Join(base, language, path, language+".tar.gz") 313 | 314 | if FileExists(tarPath) { 315 | return tarPath, nil 316 | } 317 | //Download tarball 318 | 319 | url := baseURL + "/" + path + "/" + language + ".tar.gz" 320 | if err := downloadFileDirect(tarPath, url); err != nil { 321 | return "", fmt.Errorf("failed to download sample '%s' - %v", path, err) 322 | } 323 | 324 | return tarPath, nil 325 | } 326 | -------------------------------------------------------------------------------- /pkg/aggregator/aggregator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package aggregator 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | "path/filepath" 12 | "reflect" 13 | "testing" 14 | ) 15 | 16 | const testJSONdate = "[{\"path\":\"testrepo/simple-test-test\",\"sha\":\"2c755297a2073d7f317440e8429d274b284a9051\",\"example\":{\"name\":\"Simple Test Test\",\"category\":\"Unit Test\",\"categories\":[\"TestCat\"],\"description\":\"I am a simple test\",\"author\":\"Intel Corporation\",\"date\":\"1970-01-01\",\"tag\":\"test\",\"sample_readme_uri\":\"https://test.com\"}}]" 17 | 18 | type testNewAggregatorData struct { 19 | dir string 20 | ts *httptest.Server 21 | testLanguages []string 22 | } 23 | 24 | func setupAggregatorTest(t *testing.T) *testNewAggregatorData { 25 | t.Helper() 26 | var td testNewAggregatorData 27 | 28 | //Get Cache to use 29 | dir, err := ioutil.TempDir("", "example") 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | td.dir = dir 34 | 35 | // Get a test http server 36 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | fmt.Fprintln(w, testJSONdate) 38 | })) 39 | td.ts = ts 40 | 41 | td.testLanguages = []string{"cpp"} 42 | 43 | return &td 44 | } 45 | 46 | func (tdata *testNewAggregatorData) removeLock(t *testing.T) { 47 | t.Helper() 48 | os.Remove(filepath.Join(tdata.dir, AggregatorLocalAPILevel, cacheLockName)) 49 | } 50 | 51 | func (tdata *testNewAggregatorData) cleanup() { 52 | os.RemoveAll(tdata.dir) 53 | tdata.ts.Close() 54 | } 55 | 56 | func TestNewAggregator(t *testing.T) { 57 | td := setupAggregatorTest(t) 58 | defer td.cleanup() 59 | 60 | _, err := NewAggregator("", "", []string{}, true, true) 61 | if err == nil { 62 | t.Errorf("this NewAggregator setup should have failed! With empty URL") 63 | } 64 | td.removeLock(t) 65 | 66 | _, err = NewAggregator("1asd://sd", "", []string{}, true, true) 67 | if err == nil { 68 | t.Errorf("this NewAggregator setup should have failed! With malformed URL ") 69 | } 70 | td.removeLock(t) 71 | 72 | _, err = NewAggregator("http://abcIShouldNotExist.intel.com/", "", []string{}, true, true) 73 | if err == nil { 74 | t.Errorf("this NewAggregator setup should have failed! With empty directory passed") 75 | } 76 | td.removeLock(t) 77 | 78 | // I wanted to test failing to create the local cache directory but I could think of a 79 | // way todo it: a) crossplatform b) running as admin could be valid usecase 80 | 81 | _, err = NewAggregator("http://abcIShouldNotExist.intel.com/", td.dir, []string{}, true, true) 82 | if err == nil { 83 | t.Errorf("lenth or nil lanauges array should have failed") 84 | } 85 | td.removeLock(t) 86 | 87 | _, err = NewAggregator("http://abcIShouldNotExist.intel.com/", td.dir, td.testLanguages, true, true) 88 | if err == nil { 89 | t.Errorf("should not be able to find cpp.json here, err should be network or http related") 90 | } 91 | td.removeLock(t) 92 | 93 | //404 Test (non 200) 94 | badTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 95 | w.WriteHeader(http.StatusNotFound) 96 | })) 97 | 98 | defer badTS.Close() 99 | 100 | _, err = NewAggregator(badTS.URL, td.dir, td.testLanguages, true, true) 101 | if err == nil { 102 | t.Errorf("should have failed with 404 HTTP code") 103 | } 104 | td.removeLock(t) 105 | 106 | //Server does not return JSON test 107 | badJSONTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 108 | fmt.Fprintln(w, "This is not JSON ¯\\_(ツ)_/¯ ") 109 | })) 110 | defer badJSONTS.Close() 111 | 112 | _, err = NewAggregator(badJSONTS.URL, td.dir, td.testLanguages, true, true) 113 | if err == nil { 114 | t.Errorf("should have failed garbage JSON") 115 | } 116 | td.removeLock(t) 117 | 118 | _, err = NewAggregator(td.ts.URL, td.dir, td.testLanguages, true, true) 119 | if err != nil { 120 | t.Error(err) 121 | } 122 | td.removeLock(t) 123 | 124 | } 125 | 126 | func TestGetLocalPath(t *testing.T) { 127 | td := setupAggregatorTest(t) 128 | defer td.cleanup() 129 | 130 | a, err := NewAggregator(td.ts.URL, td.dir, td.testLanguages, true, true) 131 | if err != nil { 132 | t.Error(err) 133 | } 134 | 135 | expected := filepath.Join(td.dir, AggregatorLocalAPILevel) 136 | returned := a.GetLocalPath() 137 | 138 | if returned != expected { 139 | t.Errorf("directory passed %s was not returned %s as returned by the aggregator", expected, returned) 140 | } 141 | } 142 | 143 | func TestGetURL(t *testing.T) { 144 | td := setupAggregatorTest(t) 145 | defer td.cleanup() 146 | 147 | a, err := NewAggregator(td.ts.URL, td.dir, td.testLanguages, true, true) 148 | if err != nil { 149 | t.Error(err) 150 | } 151 | 152 | returned := a.GetURL() 153 | 154 | if a.GetURL() != td.ts.URL { 155 | t.Errorf("URL passed %s was not returned %s as returned by the aggregator", td.ts.URL, returned) 156 | } 157 | } 158 | 159 | func TestGetLanguages(t *testing.T) { 160 | td := setupAggregatorTest(t) 161 | defer td.cleanup() 162 | 163 | a, err := NewAggregator(td.ts.URL, td.dir, td.testLanguages, true, true) 164 | if err != nil { 165 | t.Error(err) 166 | } 167 | 168 | returned := a.GetLanguages() 169 | if !reflect.DeepEqual(returned, td.testLanguages) { //Maybe should not test order of []string 170 | t.Errorf("URL passed %s was not returned %s as returned by the aggregator", td.testLanguages, returned) 171 | } 172 | } 173 | 174 | func TestBadTLS(t *testing.T) { 175 | td := setupAggregatorTest(t) 176 | defer td.cleanup() 177 | badTLS := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 178 | fmt.Fprintln(w, "I am a baaad guy") 179 | })) 180 | defer badTLS.Close() 181 | 182 | _, err := NewAggregator(badTLS.URL, td.dir, td.testLanguages, true, true) 183 | if err == nil { 184 | t.Errorf("should have failed due to invalid certificate") 185 | } 186 | } 187 | 188 | func TestOSFiltering(t *testing.T) { 189 | td := setupAggregatorTest(t) 190 | defer td.cleanup() 191 | td.ts.Close() 192 | 193 | //Make a more specifc test server 194 | const a = "[{\"path\":\"hpc-toolkit-samples-0af2a44aa341bf20ea53d5b908c93d467f65aacf/Nbody\",\"sha\":\"0af2a44aa341bf20ea53d5b908c93d467f65aacf\",\"example\":{\"name\":\"nbody\",\"categories\":[\"Intel\u00AE oneAPI HPC Toolkit/Segment Samples\"],\"description\":\"An N-body simulation is a simulation of a dynamical system of particles, usually under the influence of physical forces, such as gravity. This nbody sample code is implemented using C++ and SYCL language for CPU and GPU.\"}},{\"path\":\"hpc-toolkit-samples-0af2a44aa341bf20ea53d5b908c93d467f65aacf/Particle_Diffusion\",\"sha\":\"0af2a44aa341bf20ea53d5b908c93d467f65aacf\",\"example\":{\"name\":\"Particle-Diffusion\",\"categories\":[\"Intel\u00AE oneAPI HPC Toolkit/Segment Samples\"],\"description\":\"This code sample shows a simple (non-optimized) implementation of a Monte Carlo simulation of the diffusion of water molecules in tissue.\"}},{\"path\":\"hpc-toolkit-samples-0af2a44aa341bf20ea53d5b908c93d467f65aacf/iso3dfd_dpcpp\",\"sha\":\"0af2a44aa341bf20ea53d5b908c93d467f65aacf\",\"example\":{\"name\":\"ISO3DFD\",\"categories\":[\"Intel\u00AE oneAPI HPC Toolkit/Segment Samples\"],\"description\":\"A finite difference stencil kernel for solving 3D acoustic isotropic wave equation\",\"toolchain\":[\"dpcpp\"],\"os\":[\"noknownOS\"],\"sample_readme_uri\":\"https://software.intel.com/en-us/articles/code-samples-for-intel-oneapibeta-toolkits\"}},{\"path\":\"hpc-toolkit-samples-0af2a44aa341bf20ea53d5b908c93d467f65aacf/mandelbrot\",\"sha\":\"0af2a44aa341bf20ea53d5b908c93d467f65aacf\",\"example\":{\"name\":\"Mandelbrot\",\"categories\":[\"Intel\u00AE oneAPI HPC Toolkit/Segment Samples\"],\"description\":\"mandelbrot sample.\",\"os\":[\"noknownOS\"]}}]" 195 | 196 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 197 | fmt.Fprintln(w, a) 198 | })) 199 | td.ts = ts 200 | defer td.ts.Close() 201 | 202 | filtered, err := NewAggregator(td.ts.URL, td.dir, td.testLanguages, false, true) 203 | if err != nil { 204 | t.Errorf("failed to setup aggregator with good configs") 205 | } 206 | td.removeLock(t) 207 | 208 | if len(filtered.Samples[td.testLanguages[0]]) != 2 { 209 | t.Errorf("aggregator should have only seen two samples %v", len(filtered.Samples[td.testLanguages[0]])) 210 | } 211 | 212 | unFiltered, err := NewAggregator(td.ts.URL, td.dir, td.testLanguages, true, true) 213 | if err != nil { 214 | t.Errorf("failed to setup aggregator with good configs") 215 | } 216 | if len(unFiltered.Samples[td.testLanguages[0]]) != 4 { 217 | t.Errorf("aggregator should have only seen two samples") 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /pkg/aggregator/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package aggregator 5 | 6 | import ( 7 | "crypto/sha512" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "path/filepath" 15 | "time" 16 | 17 | "github.com/mattn/go-ieproxy" 18 | ) 19 | 20 | func init() { 21 | http.DefaultTransport.(*http.Transport).Proxy = ieproxy.GetProxyFunc() 22 | } 23 | 24 | //downloadFileDirect Fetchs URL into local file 25 | func downloadFileDirect(path string, url string) error { 26 | 27 | // Get the data 28 | c := &http.Client{ 29 | Timeout: HTTPTimeout * time.Second, 30 | } 31 | // Get the data 32 | resp, err := c.Get(url) 33 | if err != nil { 34 | return err 35 | } 36 | defer resp.Body.Close() 37 | if resp.StatusCode != http.StatusOK { 38 | return fmt.Errorf("HTTP-%v on %s", resp.StatusCode, url) 39 | } 40 | 41 | //Ensure Directory for local path of language exists 42 | 43 | if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil { 44 | return err 45 | } 46 | 47 | // Create the file 48 | out, err := os.Create(path) 49 | if err != nil { 50 | return err 51 | } 52 | defer out.Close() 53 | 54 | // Write the body to file 55 | _, err = io.Copy(out, resp.Body) 56 | return err 57 | } 58 | 59 | func sha512URL(url string) ([]byte, []byte, error) { 60 | 61 | c := &http.Client{ 62 | Timeout: HTTPTimeout * time.Second, 63 | } 64 | // Get the data 65 | resp, err := c.Get(url) 66 | if err != nil { 67 | return nil, nil, err 68 | } 69 | defer resp.Body.Close() 70 | if resp.StatusCode != http.StatusOK { 71 | return nil, nil, fmt.Errorf("HTTP-%v on %s", resp.StatusCode, url) 72 | } 73 | // Write the body to file 74 | hasher := sha512.New() 75 | 76 | body, err := ioutil.ReadAll(resp.Body) 77 | if err != nil { 78 | return nil, nil, err 79 | } 80 | 81 | _, err = hasher.Write(body) 82 | if err != nil { 83 | return nil, nil, nil 84 | } 85 | 86 | return hasher.Sum(nil), body, nil 87 | } 88 | 89 | func checkURL(URL string) (*url.URL, error) { 90 | return url.ParseRequestURI(URL) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/aggregator/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package aggregator 5 | 6 | import ( 7 | "crypto/sha512" 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | //FileExists helper function for checking a file exists 13 | func FileExists(path string) bool { 14 | if _, err := os.Stat(path); os.IsNotExist(err) { 15 | return false 16 | } 17 | return true 18 | } 19 | 20 | //localhash returns hash, error 21 | func localHash(path string) ([]byte, error) { 22 | hasher := sha512.New() 23 | local, err := ioutil.ReadFile(path) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | _, err = hasher.Write(local) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return hasher.Sum(nil), nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/aggregator/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package aggregator 5 | 6 | // Sample Type 7 | type Sample struct { 8 | Path string `json:"path"` 9 | SHA string `json:"sha"` 10 | Fields Fields `json:"example"` 11 | } 12 | 13 | // Fields type (nested struct in sample type) 14 | type Fields struct { 15 | Name string `json:"name"` 16 | Description string `json:"description"` 17 | Categories []string `json:"categories"` 18 | Author string `json:"author"` 19 | Date string `json:"date"` 20 | Tag string `json:"tag"` 21 | Dependencies []string `json:"dependencies"` 22 | OS []string `json:"os"` 23 | ReadmeURI string `json:"sample_readme_uri"` 24 | TargetDevice []string `json:"targetDevice"` 25 | Builder []string `json:"builder"` 26 | Toolchain []string `json:"toolchain"` 27 | 28 | //Not Parsing these out atm 29 | ProjectOptions []interface{} `json:"projectOptions"` 30 | MakeVariables map[string]interface{} 31 | IndexerVariables map[string]interface{} 32 | } 33 | -------------------------------------------------------------------------------- /pkg/browser/browser_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package browser 5 | 6 | import ( 7 | exec "golang.org/x/sys/execabs" 8 | ) 9 | 10 | //OpenBrowser opens the url passed 11 | func OpenBrowser(url string) error { 12 | cmd := exec.Command("open", url) 13 | return cmd.Run() 14 | } 15 | -------------------------------------------------------------------------------- /pkg/browser/browser_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package browser 5 | 6 | import ( 7 | exec "golang.org/x/sys/execabs" 8 | ) 9 | 10 | //OpenBrowser opens the url passed 11 | func OpenBrowser(url string) error { 12 | cmd := exec.Command("xdg-open", url) 13 | //Maybe we need to disown the process, but we should be better 14 | //than just calling run. 15 | return cmd.Start() 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pkg/browser/browser_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package browser 5 | 6 | import ( 7 | exec "golang.org/x/sys/execabs" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "syscall" 12 | ) 13 | 14 | //OpenBrowser opens the url passed 15 | func OpenBrowser(url string) error { 16 | r := strings.NewReplacer("&", "^&") 17 | rundll32 := filepath.Join(os.Getenv("SystemRoot"), "System32", "rundll32.exe") 18 | 19 | cmd := exec.Command(rundll32, "url.dll,FileProtocolHandler", r.Replace(url)) 20 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 21 | return cmd.Run() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /pkg/deps/checker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package deps 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "regexp" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | const ( 18 | rootEnvKey = "ONEAPI_ROOT" 19 | 20 | baseURL = "https://www.intel.com/content/www/us/en/developer/tools/oneapi/overview" 21 | 22 | formatStr = `The following tools are needed to build this sample but are not locally installed: (%s) 23 | You may continue and view the sample without the prerequisites. To install the missing prerequisites, visit: 24 | %s%s 25 | ` 26 | 27 | pkgReg = "pkg\\|([^|]*)\\|?(.*)" 28 | compilerReg = "compiler\\|(.*)" 29 | ) 30 | 31 | // CheckDeps as 32 | func CheckDeps(dependencies []string, root string) (msg string, errCode int) { 33 | //dependencies are both "normal" component dependencies ( ["mkl", "vtune"]) 34 | //and "special" dependencies ( ["pkg|mraa", "compiler|icc"]) 35 | componentDeps, specialDeps := separatethSheepsGoats(dependencies) 36 | 37 | componentMsg, componentErrCode := checkComponentDeps(componentDeps, root) 38 | specialMsg, specialErrCode := checkSpecialDeps(specialDeps, root) 39 | 40 | //now gather results and return 41 | msg, errCode = simplifyMsgErrCode(specialMsg, specialErrCode, componentMsg, componentErrCode) 42 | return msg, errCode 43 | } 44 | func simplifyMsgErrCode(msg1 string, errCode1 int, msg2 string, errCode2 int) (msg string, errCode int) { 45 | //takes a two pairs of messages and error codes and returns their concatenation (or whatever is appropriate) 46 | msg = "" 47 | errCode = 0 48 | divider := "" 49 | 50 | if errCode1 != 0 { 51 | msg = msg1 52 | errCode = errCode1 53 | divider = "\n" 54 | } 55 | if errCode2 != 0 { 56 | msg = msg + divider + msg2 57 | errCode = errCode2 58 | } 59 | return msg, errCode 60 | } 61 | 62 | func checkComponentDeps(dependencies []string, root string) (msg string, errCode int) { 63 | 64 | var missing []string 65 | for _, k := range dependencies { 66 | if _, err := os.Stat(filepath.Join(root, k)); os.IsNotExist(err) { 67 | //does NOT EXIST 68 | missing = append(missing, k) 69 | } 70 | } 71 | // Something was missing, get a message 72 | if len(missing) > 0 { 73 | msg := GenerateMessage(missing) 74 | return msg, -1 75 | } 76 | return "", 0 77 | } 78 | 79 | func checkPackageDeps(packageDeps []string, root string) (msg string, errCode int) { 80 | // deps = pckg||url 81 | // 1. check for pkg-config 82 | // 1.F if not: message returned says "this sample requires which we unable to verify. Be sure it is installed. ." 83 | // 1.T if so: call `pkg-config --exists ` 84 | // 1.T.T if exists - OK, no message 85 | // 1.T.F if not: "this sample requires which is not installed. You can obtain it here: " 86 | 87 | //errCode -1 no package , -2 no pkg-config 88 | 89 | msg = "" 90 | errCode = 0 91 | divider := "" 92 | 93 | //0. setup regex that will parse dependency 94 | regEx := regexp.MustCompile(pkgReg) 95 | 96 | //1. 97 | _, pkgErr := exec.LookPath("pkg-config") 98 | 99 | for _, dep := range packageDeps { 100 | pkg, url := parseDep(regEx, dep) 101 | err := pkgErr 102 | if err == nil { 103 | cmd := exec.Command("pkg-config", "--exists", pkg) 104 | err = cmd.Run() 105 | if err == nil { 106 | //we are good to go. 107 | } else { 108 | msg = msg + divider + fmt.Sprintf("this sample requires %s which is not installed. To obtain: %s", pkg, url) 109 | divider = "\n" 110 | errCode = -1 111 | } 112 | } else { 113 | msg = msg + divider + fmt.Sprintf("this sample requires %s which we are unable to verify. Please make sure it is installed. %s", pkg, url) 114 | divider = "\n" 115 | errCode = -2 116 | } 117 | } 118 | 119 | return msg, errCode 120 | } 121 | 122 | func parseDep(re *regexp.Regexp, dep string) (pkg string, url string) { 123 | //given a regex and a string, parses it out to package and url. 124 | // this parser can be used for both pkg| and compiler| , use constants pkgReg or compilerReg as first arg 125 | // example: parseDep(pgkReg, "pkg|mraa|www.intel.com") => "mraa", "www.intel.com" 126 | // parseDep(compilerReg, "compiler|icc") => "icc", "" 127 | match := re.FindStringSubmatch(dep) 128 | pkg = "" 129 | 130 | if len(match) > 1 { 131 | pkg = match[1] 132 | url = fmt.Sprintf("Search for 'install %s' for help.", pkg) 133 | } 134 | if len(match) > 2 { 135 | url = match[2] 136 | } 137 | return pkg, url 138 | } 139 | 140 | func checkCompilerDeps(compilerDeps []string, root string) (msg string, errCode int) { 141 | 142 | msg = "" 143 | errCode = 0 144 | var missing []string 145 | 146 | winCompilers := map[string]string{ 147 | "icc": "bin/intel64/icl.exe", 148 | "fortran": "bin/intel64/ifort.exe", 149 | "dpcpp": "bin/dpcpp.exe", 150 | "icpc": "bin/intel64/icpc.exe", 151 | "icx": "bin/icx.exe", 152 | "icpcx": "bin/icpcx.exe", 153 | "ifx": "bin/ifx.exe", 154 | } 155 | linCompilers := map[string]string{ 156 | "icc": "bin/intel64/icc", 157 | "fortran": "bin/intel64/ifort", 158 | "dpcpp": "bin/dpcpp", 159 | "icpc": "bin/intel64/icpc", 160 | "icx": "bin/icx", 161 | "icpcx": "bin/icpcx", 162 | "ifx": "bin/ifx", 163 | } 164 | macCompilers := map[string]string{ 165 | "icpc": "bin/intel64/icpc", 166 | "icc": "bin/intel64/icc", 167 | "ifort": "bin/intel64/ifort", 168 | "icx": "bin/icx", 169 | "icpcx": "bin/icpcx", 170 | "ifx": "bin/ifx", 171 | } 172 | 173 | compilerRoot := GetCompilerRoot(root) 174 | 175 | //0. setup regex that will parse dependency 176 | regEx := regexp.MustCompile(compilerReg) 177 | 178 | for _, dep := range compilerDeps { 179 | compiler, _ := parseDep(regEx, dep) 180 | var pathTail string 181 | 182 | switch runtime.GOOS { 183 | case "linux": 184 | pathTail = linCompilers[compiler] 185 | case "windows": 186 | pathTail = winCompilers[compiler] 187 | case "darwin": 188 | pathTail = macCompilers[compiler] 189 | default: 190 | msg = "Cannot check Compiler, unsupported OS" 191 | errCode = 01 192 | return msg, errCode 193 | } 194 | 195 | fullPath := filepath.Join(compilerRoot, pathTail) 196 | if pathTail == "" || !fileExists(fullPath) { 197 | missing = append(missing, compiler) 198 | } 199 | } 200 | if len(missing) > 0 { 201 | msg = GenerateMessage(missing) 202 | errCode = 01 203 | } 204 | return msg, errCode 205 | } 206 | 207 | func fileExists(path string) bool { 208 | _, err := os.Stat(path) 209 | return !os.IsNotExist(err) 210 | } 211 | 212 | func checkSpecialDeps(specialDependencies []string, root string) (msg string, errCode int) { 213 | packageDeps, remainingDeps := separatethSheepsGoatsRhematosC(specialDependencies, func(dep string) bool { return strings.HasPrefix(dep, "pkg|") }) 214 | compilerDeps, remainingDeps := separatethSheepsGoatsRhematosC(remainingDeps, func(dep string) bool { return strings.HasPrefix(dep, "compiler|") }) 215 | 216 | packageMsg, packageErrCode := checkPackageDeps(packageDeps, root) 217 | compilerMsg, compilerErrCode := checkCompilerDeps(compilerDeps, root) 218 | msg, errCode = simplifyMsgErrCode(packageMsg, packageErrCode, compilerMsg, compilerErrCode) 219 | return msg, errCode 220 | } 221 | 222 | // GetOneAPIRoot gets the root the OneAPI installation 223 | // based on the ONEAPI_ROOT 224 | func GetOneAPIRoot() (path string, err error) { 225 | root, ok := os.LookupEnv(rootEnvKey) 226 | 227 | if !ok { 228 | return "", fmt.Errorf("%s not defined. Be sure to run oneapi environment script ( source setvars.sh )", rootEnvKey) 229 | } 230 | 231 | tmp := filepath.Dir(root) 232 | tmp = strings.ToLower(filepath.Base(tmp)) 233 | if tmp == "oneapi" { 234 | return filepath.Dir(root), nil 235 | } 236 | 237 | return root, nil 238 | } 239 | 240 | // CMPLR_ROOT was, for awhile, always defined in the environment. But no longer. 241 | func GetCompilerRoot(root string) (compilerRoot string) { 242 | compilerRoot = filepath.Join(root, "compiler", "latest") 243 | return compilerRoot 244 | } 245 | 246 | type suiteComponent struct { 247 | SuiteId string `json:"suiteId"` 248 | ComponentId string `json:"componentId"` 249 | Primary bool `json:"primary"` 250 | } 251 | 252 | type compDir struct { 253 | Dir string `json:"dir"` 254 | ComponentId string `json:"componentId"` 255 | } 256 | 257 | type suite struct { 258 | SuiteId string `json:"id"` 259 | Label string `json:"label"` 260 | UrlSlug string `json:"urlSlug"` 261 | BaseToolkit string `json:"baseToolkit"` 262 | } 263 | 264 | func GenerateMessage(missing []string) string { 265 | 266 | //1.0 read in compmapping.json 267 | //1.1 - translate missing to id list 268 | //2.0 read in suite-components.json 269 | //2.1 expand id-list to toolkits 270 | //3.0 count/find most frequent toolkit (if any) 271 | //4.0 read suites.json 272 | //4.1 get slug from toolkit 273 | 274 | //5. return message with url. 275 | 276 | //baseURL := "https://www.intel.com/content/www/us/en/developer/tools/oneapi/overview" // this is captured in format String 277 | var slug string 278 | fallbackMsg := fmt.Sprintf(formatStr, strings.Join(missing, " "), baseURL, slug) 279 | 280 | //1 read in compmapping.json 281 | var mapping []compDir 282 | //err := readSomeJSON(filepath.Join("json", "compmapping.json"), &mapping) 283 | err := parseSomeJSON(compmappingJSON, &mapping) 284 | if err != nil { 285 | return fallbackMsg 286 | } 287 | //1.1 translate missing to id list 288 | idList := mapStringArr(missing, func(miss string) string { 289 | for _, v := range mapping { 290 | if v.Dir == miss { 291 | return v.ComponentId 292 | } 293 | } 294 | return "" 295 | }) 296 | 297 | //2.0 read in suite-components.json 298 | var sweetComps []suiteComponent 299 | //err = readSomeJSON(filepath.Join("json", "suite-components.json"), &sweetComps) 300 | err = parseSomeJSON(sweetComponentsJSON, &sweetComps) 301 | if err != nil { 302 | return fallbackMsg 303 | } 304 | //2.1 expand id-list to toolkits 305 | 306 | var matchedSuite []string 307 | 308 | for _, suiteComp := range sweetComps { 309 | if contains(idList, suiteComp.ComponentId) { 310 | matchedSuite = append(matchedSuite, suiteComp.SuiteId) 311 | } 312 | } 313 | 314 | if len(matchedSuite) > 1 { 315 | //4.0 read suites.json 316 | var suites []suite 317 | //err = readSomeJSON(filepath.Join("json", "suites.json"), &suites) 318 | err = parseSomeJSON(suitesJSON, &suites) 319 | if err != nil { 320 | return fallbackMsg 321 | } 322 | 323 | var composed string 324 | 325 | for _, suite := range matchedSuite { 326 | slug := findSlug(suites, suite) 327 | 328 | if len(composed) == 0 { 329 | composed = fmt.Sprintf(formatStr, strings.Join(missing, " "), baseURL, slug) 330 | } else { 331 | composed = fmt.Sprintf("%sor %s%s", composed, baseURL, slug) 332 | } 333 | } 334 | return composed 335 | } 336 | 337 | return fallbackMsg 338 | } 339 | 340 | func readSomeJSON(path string, something interface{}) error { 341 | jsonFile, err := os.Open(path) 342 | if err != nil { 343 | return err 344 | } 345 | byteValue, err := ioutil.ReadAll(jsonFile) 346 | if err != nil { 347 | return err 348 | } 349 | err = json.Unmarshal(byteValue, something) 350 | if err != nil { 351 | return err 352 | } 353 | return nil 354 | } 355 | 356 | // keeping the same interface as the file reading function for now. 357 | func parseSomeJSON(str string, something interface{}) error { 358 | err := json.Unmarshal([]byte(str), something) 359 | if err != nil { 360 | return err 361 | } 362 | return nil 363 | } 364 | 365 | func mapStringArr(arr []string, f func(a string) string) []string { 366 | var result []string 367 | for _, v := range arr { 368 | result = append(result, f(v)) 369 | } 370 | return result 371 | } 372 | 373 | func contains(arr []string, needle string) bool { 374 | for _, v := range arr { 375 | if needle == v { 376 | return true 377 | } 378 | } 379 | return false 380 | } 381 | 382 | func findSlug(suites []suite, maxSuite string) string { 383 | for _, aSuite := range suites { 384 | if aSuite.SuiteId == maxSuite { 385 | return aSuite.UrlSlug 386 | } 387 | } 388 | //not worth dealing with the error 389 | return "" 390 | } 391 | 392 | func separatethSheepsGoats(dependencies []string) ([]string, []string) { 393 | // the dependencies consist of "normal" component dependencies ( ["mkl", "vtune"]) i.e. "sheep" 394 | // and "special" dependencies ( ["pkg|mraa", "compiler|icc"]) i.e. "goats" 395 | // as foretold, they are separateth into two groups. 396 | return separatethSheepsGoatsRhematosC(dependencies, func(dep string) bool { return !strings.Contains(dep, "|") }) 397 | } 398 | 399 | func separatethSheepsGoatsRhematosC(dependencies []string, predicate func(a string) bool) ([]string, []string) { 400 | // And before him shall be gathered all nations, and he shall separate them one from another as a shepherd separateth the sheep from the goats. 401 | // this function takes a divine predicate and uses that to split a string array in twain 402 | var sheep []string 403 | var goats []string 404 | for _, dep := range dependencies { 405 | if predicate(dep) { 406 | sheep = append(sheep, dep) 407 | } else { 408 | goats = append(goats, dep) 409 | } 410 | } 411 | return sheep, goats 412 | } 413 | -------------------------------------------------------------------------------- /pkg/deps/checker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package deps 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "reflect" 11 | "regexp" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | func TestGetOneAPIRoot(t *testing.T) { 17 | 18 | os.Unsetenv(rootEnvKey) //Clear Env, just incase 19 | _, err := GetOneAPIRoot() 20 | if err == nil { 21 | t.Errorf("should have failed to find the env key, we unset it for test") 22 | } 23 | 24 | const testValue = "/opt/inteltestval/" 25 | os.Setenv(rootEnvKey, testValue) 26 | val, err := GetOneAPIRoot() 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | if val != testValue { 31 | t.Errorf("unexpected env value receieved") 32 | } 33 | os.Unsetenv(rootEnvKey) //Clear Env, just incase 34 | } 35 | 36 | func setupTestRoot(t *testing.T, testDeps []string) (root string) { 37 | t.Helper() 38 | root, err := ioutil.TempDir("", "depscheck") 39 | if err != nil { 40 | t.Error(err) 41 | 42 | } 43 | for _, k := range testDeps { 44 | err := os.MkdirAll(filepath.Join(root, k), os.ModePerm) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | } 49 | return root 50 | } 51 | 52 | func TestCheckDeps(t *testing.T) { 53 | 54 | testingGold := []string{"cheese", "milk"} 55 | 56 | root := setupTestRoot(t, testingGold) 57 | 58 | msg, errCode := CheckDeps(testingGold, root) 59 | if errCode > 0 { 60 | t.Errorf("Golden test failed found missing %s", msg) 61 | } 62 | 63 | } 64 | 65 | func TestGenerateMessage(t *testing.T) { 66 | missing := []string{"foo"} 67 | msg := GenerateMessage(missing) 68 | fmt.Println(msg) 69 | 70 | if !strings.Contains(msg, "(foo)") { 71 | t.Errorf("foo wasn't reported") 72 | } 73 | } 74 | 75 | func TestCheckCompilerDeps(t *testing.T) { 76 | testingGold := []string{"cheese", "milk"} 77 | 78 | root := setupTestRoot(t, testingGold) 79 | deps := []string{"compiler|gomer"} 80 | _, errCode := checkCompilerDeps(deps, root) 81 | if errCode == 0 { 82 | t.Errorf("gomer compiler should never have been found") 83 | } 84 | } 85 | 86 | /* 87 | func TestReadSomeJSONAndTheJSON(t *testing.T) { 88 | 89 | var mapping []compDir 90 | err := readSomeJSON(filepath.Join("..", "..", "json", "compmapping.json"), &mapping) 91 | if err != nil { 92 | t.Errorf("failure loading compmapping.json - %s", err) 93 | } 94 | 95 | var sweetComps []suiteComponent 96 | err = readSomeJSON(filepath.Join("..", "..", "json", "suite-components.json"), &sweetComps) 97 | if err != nil { 98 | t.Errorf("failure loading suite-components.json - %s", err) 99 | } 100 | 101 | var suites []suite 102 | err = readSomeJSON(filepath.Join("..", "..", "json", "suites.json"), &suites) 103 | if err != nil { 104 | t.Errorf("failure loading suites.json - %s", err) 105 | } 106 | 107 | //not worrying about invalid-components-hosts or components.json right now. 108 | } 109 | */ 110 | 111 | func TestParseSomeJSONAndTheJSON(t *testing.T) { 112 | var mapping []compDir 113 | err := parseSomeJSON(compmappingJSON, &mapping) 114 | if err != nil { 115 | t.Errorf("failure parsing Compmappingjson - %s", err) 116 | } 117 | if len(mapping) == 0 { 118 | t.Errorf("compmappingJSON empty after parsing") 119 | } 120 | 121 | var sweetComps []suiteComponent 122 | err = parseSomeJSON(sweetComponentsJSON, &sweetComps) 123 | if err != nil { 124 | t.Errorf("failure parsing sweetComponentsJSON - %s", err) 125 | } 126 | if len(sweetComps) == 0 { 127 | t.Errorf("sweetComponentsJSON empty after parsing") 128 | } 129 | 130 | var suites []suite 131 | err = parseSomeJSON(suitesJSON, &suites) 132 | if err != nil { 133 | t.Errorf("failure parsing suitesJSON - %s", err) 134 | } 135 | if len(suites) == 0 { 136 | t.Errorf("suitesJSON empty after parsing") 137 | } 138 | } 139 | 140 | func TestContains(t *testing.T) { 141 | haystack := []string{"marvel", "crunch", "america", "picard"} 142 | needle := "picard" 143 | youshouldlivesolong := "ryker" 144 | wat := "" 145 | 146 | if !contains(haystack, needle) { 147 | t.Errorf("%s not found", needle) 148 | } 149 | 150 | if contains(haystack, youshouldlivesolong) { 151 | t.Errorf("captain %s?? Never", youshouldlivesolong) 152 | } 153 | 154 | if contains(haystack, wat) { 155 | t.Errorf("Alfred North Whitehead hates you") 156 | } 157 | } 158 | 159 | func TestMapStringArr(t *testing.T) { 160 | haystack := []string{"marvel", "crunch", "america", "picard"} 161 | fuzz := mapStringArr(haystack, func(a string) string { 162 | return a[0:1] 163 | }) 164 | expectation := []string{"m", "c", "a", "p"} 165 | if !reflect.DeepEqual(fuzz, expectation) { 166 | t.Errorf("string mapping not that hard, maybe consider career change?") 167 | } 168 | 169 | emptyArr := []string{} 170 | ress := mapStringArr(emptyArr, func(a string) string { 171 | return a 172 | }) 173 | if len(ress) > 0 { 174 | t.Errorf("string mapping not that hard, maybe consider career change?") 175 | } 176 | //amazing enuogh, DeepEqual returns the wrong value. These are both empty, it says not equal. 177 | // if !reflect.DeepEqual(emptyArr, ress) { 178 | // t.Errorf("string mapping not that hard, maybe consider career change? %s %s", emptyArr, ress) 179 | // } 180 | 181 | } 182 | 183 | func findDirectory(mapping []compDir, componentId string) string { 184 | for _, compDir := range mapping { 185 | if compDir.ComponentId == componentId { 186 | return compDir.Dir 187 | } 188 | } 189 | return "" 190 | } 191 | 192 | func TestFindDir(t *testing.T) { 193 | var mapping []compDir 194 | compMapErr := parseSomeJSON(compmappingJSON, &mapping) 195 | 196 | dir := findDirectory(mapping, "intel_advisor") 197 | if dir == "" { 198 | t.Errorf("findDirectory failed, unable to locate advisor in compmappingJSON") 199 | } 200 | 201 | dir = findDirectory(mapping, "xanadu") 202 | if dir != "" { 203 | t.Errorf("In Xanadu did Kubla Khan a stately pleasure dome decree") 204 | } 205 | 206 | if compMapErr != nil { 207 | t.Errorf("failure parsing the JSON constants compmappingJSON: %s", compMapErr) 208 | } 209 | } 210 | 211 | func TestIntegrityOfJSONConstants(t *testing.T) { 212 | // we have a test above that makes sure the constants (suitesJSON, sweetComponentJSON, etc) 213 | // are valid JSON strings. 214 | // this test makes sure that every entry in sweetComponentsJSON has a matching component id in componentsMapping 215 | // extra entries in componentMapping is ok, there may be vestigial components there. Doesn't matter. 216 | // and that every entry in sweetComponentJSON has a suite in suitesJSON, and vice versa. 217 | 218 | var mapping []compDir 219 | compMapErr := parseSomeJSON(compmappingJSON, &mapping) 220 | 221 | var sweetComps []suiteComponent 222 | sweetCompErr := parseSomeJSON(sweetComponentsJSON, &sweetComps) 223 | 224 | var suites []suite 225 | suitesErr := parseSomeJSON(suitesJSON, &suites) 226 | 227 | if compMapErr != nil || sweetCompErr != nil || suitesErr != nil { 228 | t.Errorf("failure parsing the JSON constants compmappingJSON: %s - sweetComponentsJSON: %s - suitesJSON: %s", compMapErr, sweetCompErr, suitesErr) 229 | } 230 | 231 | //run through sweetComps looking for id that is not in compDir map 232 | var dir string 233 | for _, suiteComp := range sweetComps { 234 | dir = findDirectory(mapping, suiteComp.ComponentId) 235 | if dir == "" { 236 | t.Errorf("unable to locate directory in compmapping JSON for component declared in sweetComponentsJSON: %s", suiteComp.ComponentId) 237 | } 238 | } 239 | 240 | //run through sweetComps looking for suite that is not in suitesJSON 241 | var slug string 242 | for _, sweetComp := range sweetComps { 243 | slug = findSlug(suites, sweetComp.SuiteId) 244 | if slug == "" { 245 | t.Errorf("unable to locate suite in suitesJSON for entry declared in sweetComponentsJSON: %s", sweetComp.SuiteId) 246 | } 247 | } 248 | 249 | //run through suites looking for suite that is not in sweetComponentsJSON 250 | for _, suiteEntry := range suites { 251 | match := false 252 | for _, sweetComp := range sweetComps { 253 | if sweetComp.SuiteId == suiteEntry.SuiteId { 254 | match = true 255 | break 256 | } 257 | } 258 | if !match { 259 | t.Errorf("unable to locate suite in sweetComponentsJSON for entry declared in sutiesJSON: %s", suiteEntry.SuiteId) 260 | } 261 | } 262 | 263 | } 264 | 265 | func TestSeparatethSheepsGoats(t *testing.T) { 266 | //violates Deuteronomy 6:16 . Invoke at your peril. 267 | sheep := []string{"Dolly", "Montauciel", "Methuselina", "Lance-Corporal-Derby-XXX"} 268 | goats := []string{"Goat|Billy", "Goat|Pan", "Goat|Nanny", "Goat|Rudy-Giuliani"} 269 | 270 | components, _ := separatethSheepsGoats(sheep) 271 | if !reflect.DeepEqual(components, sheep) { 272 | t.Errorf("repent!") 273 | } 274 | 275 | _, special := separatethSheepsGoats(goats) 276 | if !reflect.DeepEqual(special, goats) { 277 | t.Errorf("repent!") 278 | } 279 | 280 | herd := append(sheep, goats...) 281 | 282 | components, special = separatethSheepsGoats(herd) 283 | if !reflect.DeepEqual(components, sheep) { 284 | t.Errorf("repent!") 285 | } 286 | if !reflect.DeepEqual(special, goats) { 287 | t.Errorf("repent!") 288 | } 289 | } 290 | 291 | func TestSimplifyMsgErrCode(t *testing.T) { 292 | msg, errCode := simplifyMsgErrCode("yams", 0, "hams", 0) 293 | if errCode != 0 { 294 | t.Errorf("errCode should be 0") 295 | } 296 | 297 | msg, errCode = simplifyMsgErrCode("yams", 1, "hams", 0) 298 | if msg != "yams" { 299 | t.Errorf("should have yams") 300 | } 301 | 302 | msg, errCode = simplifyMsgErrCode("yams", 0, "hams", 1) 303 | if msg != "hams" { 304 | t.Errorf("should have hams") 305 | } 306 | 307 | msg, errCode = simplifyMsgErrCode("yams", 1, "hams", 1) 308 | if msg != "yams\nhams" { 309 | t.Errorf("should have yams and hams") 310 | } 311 | } 312 | 313 | func TestParseDep(t *testing.T) { 314 | regEx := regexp.MustCompile(pkgReg) 315 | pkg, url := parseDep(regEx, "pkg|mraa|http://www.intel.com") 316 | if pkg != "mraa" { 317 | t.Errorf("pkg should have parsed to mraa, %s", pkg) 318 | } 319 | if url != "http://www.intel.com" { 320 | t.Errorf("url should have parsed to http://www.intel.com, %s", url) 321 | } 322 | 323 | //url optional 324 | pkg, url = parseDep(regEx, "pkg|npm") 325 | if pkg != "npm" { 326 | t.Errorf("pkg should have parsed to npm, %s", pkg) 327 | } 328 | 329 | regEx = regexp.MustCompile(compilerReg) 330 | compiler, _ := parseDep(regEx, "compiler|icc") 331 | if compiler != "icc" { 332 | t.Errorf("compiler should have parsed to icc, %s", compiler) 333 | } 334 | } 335 | 336 | func TestFileExists(t *testing.T) { 337 | no := fileExists("") 338 | if no { 339 | t.Errorf("why does '' exist?") 340 | } 341 | yes := fileExists(".") 342 | if !yes { 343 | t.Errorf("why doesn't '.' exist?") 344 | } 345 | 346 | } 347 | -------------------------------------------------------------------------------- /pkg/deps/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package deps 4 | 5 | const compmappingJSON = ` 6 | [ 7 | {"componentId":"intel_advisor", "dir":"advisor"}, 8 | {"componentId":"collective_communications_library", "dir":"ccl"}, 9 | {"componentId":"intel_cluster_checker", "dir":"clck"}, 10 | {"componentId":"intel_data_analytics_library", "dir":"daal"}, 11 | {"componentId":"1a_debugger", "dir":"debugger"}, 12 | {"componentId":"dev-utilities", "dir":"dev-utilities"}, 13 | {"componentId":"deep_neural_network", "dir":"oneDNN"}, 14 | {"componentId":"dpcpp_compatibility_tool", "dir":"dpcpp-ct"}, 15 | {"componentId":"eclipse-iot-plugins", "dir":"eclipse-iot-plugins"}, 16 | {"componentId":"intel_embree", "dir":"embree"}, 17 | {"componentId":"gstreamer_plugins", "dir":"gvaplugins"}, 18 | {"componentId":"intel_inspector", "dir":"inspector"}, 19 | {"componentId":"eclipse_based_ide", "dir":"intel-eclipse-ide"}, 20 | {"componentId":"python", "dir":"intelpython"}, 21 | {"componentId":"intel_integrated_performance_primitives", "dir":"ipp"}, 22 | {"componentId":"intel_trace_analyzer_and_collector", "dir":"itac"}, 23 | {"componentId":"intel_math_kernel_library", "dir":"mkl"}, 24 | {"componentId":"intel_mpi_library", "dir":"mpi"}, 25 | {"componentId":"open_image_denoise", "dir":"oidn"}, 26 | {"componentId":"openvino", "dir":"openvino"}, 27 | {"componentId":"open_volume_kernel_library", "dir":"openvkl"}, 28 | {"componentId":"intel_ospray", "dir":"ospray"}, 29 | {"componentId":"pstl", "dir":"pstl"}, 30 | {"componentId":"intel_pytorch", "dir":"pytorch"}, 31 | {"componentId":"intel_socwatch", "dir":"socwatch"}, 32 | {"componentId":"intel_system_debugger", "dir":"system_debugger"}, 33 | {"componentId":"intel_threading_building_blocks", "dir":"tbb"}, 34 | {"componentId":"intel_tensorflow", "dir":"tensorflow"}, 35 | {"componentId":"vpl", "dir":"vpl"}, 36 | {"componentId":"intel_vtune_amplifier", "dir":"vtune"}, 37 | 38 | 39 | {"componentId":"intel_cpp_compiler", "dir":"icc"}, 40 | {"componentId":"openmp_fortran", "dir":"fortran"}, 41 | {"componentId":"dppcpp_compiler", "dir":"dpcpp"}, 42 | {"componentId":"dpcpp_library", "dir":"?"}, 43 | 44 | 45 | {"componentId":"intel_iot_connect_upm_mraa_cloud_connectors", "dir":"?"}, 46 | {"componentId":"linux_kernel_build_tools", "dir":"?"}, 47 | 48 | 49 | {"componentId":"gnu_project_debugger_gdb", "dir":"?"}, 50 | 51 | {"componentId":"linux_iot_application_development_using_containerized_toolchains", "dir":"?"} 52 | 53 | 54 | ] 55 | ` 56 | 57 | //if copy/pasting JSON from the WebConfigurator team, I recommend linting it first. 58 | // https://jsonlint.com/ 59 | // they have lots of small problems (missing quotes, stray commas, missing commas) that 60 | // Node.js forgives. 61 | const sweetComponentsJSON = ` 62 | [ 63 | 64 | { "suiteId": "HPCKit", "componentId": "intel_cpp_compiler", "primary": true }, 65 | { "suiteId": "HPCKit", "componentId": "openmp_fortran", "primary": true }, 66 | { "suiteId": "HPCKit", "componentId": "intel_mpi_library", "primary": true }, 67 | { "suiteId": "HPCKit", "componentId": "intel_inspector", "primary": true }, 68 | { "suiteId": "HPCKit", "componentId": "intel_trace_analyzer_and_collector", "primary": true }, 69 | { "suiteId": "HPCKit", "componentId": "intel_cluster_checker", "primary": true }, 70 | 71 | { "suiteId": "IOTKit", "componentId": "intel_cpp_compiler", "primary": true }, 72 | { "suiteId": "IOTKit", "componentId": "intel_inspector", "primary": true }, 73 | { 74 | "suiteId": "IOTKit", 75 | "componentId": "linux_iot_application_development_using_containerized_toolchains", 76 | "primary": true 77 | }, 78 | { "suiteId": "IOTKit", "componentId": "eclipse_based_ide", "primary": true }, 79 | { "suiteId": "IOTKit", "componentId": "linux_kernel_build_tools", "primary": true }, 80 | { "suiteId": "IOTKit", "componentId": "intel_system_debugger", "primary": true }, 81 | 82 | { "suiteId": "BringupKit", "componentId": "intel_socwatch", "primary": true }, 83 | { "suiteId": "BringupKit", "componentId": "intel_system_debugger", "primary": true }, 84 | 85 | 86 | { "suiteId": "DLDevKit", "componentId": "collective_communications_library", "primary": true }, 87 | { "suiteId": "DLDevKit", "componentId": "deep_neural_network", "primary": true }, 88 | 89 | { "suiteId": "AIKit", "componentId": "intel_tensorflow", "primary": true }, 90 | { "suiteId": "AIKit", "componentId": "intel_pytorch", "primary": true }, 91 | { "suiteId": "AIKit", "componentId": "python", "primary": true }, 92 | 93 | { "suiteId": "oneAPIKit", "componentId": "dppcpp_compiler", "primary": true }, 94 | { "suiteId": "oneAPIKit", "componentId": "dpcpp_compatibility_tool", "primary": true }, 95 | { "suiteId": "oneAPIKit", "componentId": "dpcpp_library", "primary": true }, 96 | { "suiteId": "oneAPIKit", "componentId": "1a_debugger", "primary": true }, 97 | { "suiteId": "oneAPIKit", "componentId": "intel_math_kernel_library", "primary": true }, 98 | { "suiteId": "oneAPIKit", "componentId": "intel_threading_building_blocks", "primary": true }, 99 | { "suiteId": "oneAPIKit", "componentId": "intel_integrated_performance_primitives", "primary": true }, 100 | { "suiteId": "oneAPIKit", "componentId": "intel_data_analytics_library", "primary": true }, 101 | { "suiteId": "oneAPIKit", "componentId": "python", "primary": true }, 102 | { "suiteId": "oneAPIKit", "componentId": "intel_advisor", "primary": true }, 103 | { "suiteId": "oneAPIKit", "componentId": "deep_neural_network", "primary": true }, 104 | { "suiteId": "oneAPIKit", "componentId": "collective_communications_library", "primary": true }, 105 | { "suiteId": "oneAPIKit", "componentId": "vpl", "primary": true }, 106 | 107 | { "suiteId": "RenderKit", "componentId": "intel_embree", "primary": true }, 108 | { "suiteId": "RenderKit", "componentId": "intel_ospray", "primary": true }, 109 | { "suiteId": "RenderKit", "componentId": "open_image_denoise", "primary": true }, 110 | { "suiteId": "RenderKit", "componentId": "open_volume_kernel_library", "primary": true }, 111 | 112 | { "suiteId": "VTuneProfiler", "componentId": "intel_vtune_amplifier", "primary": true } 113 | ] 114 | ` 115 | 116 | const suitesJSON = ` 117 | [ 118 | { "id": "HPCKit", "label": "Intel® oneAPI HPC Toolkit", "urlSlug": "hpc-kit", "baseToolkit": "dependency" }, 119 | { "id": "IOTKit", "label": "Intel® oneAPI IoT Toolkit", "urlSlug": "iot-kit", "baseToolkit": "dependency" }, 120 | { "id": "BringupKit", "label": "Intel® System Bring-Up Toolkit", "urlSlug": "bringup-kit", "baseToolkit": "recommended" }, 121 | { "id": "DLDevKit", "label": "Intel® oneAPI DL Framework Developer Toolkit", "urlSlug": "dldev-kit", "baseToolkit": "dependency" }, 122 | { "id": "AIKit", "label": "Intel® oneAPI AI Analytics Toolkit", "urlSlug": "ai-kit", "baseToolkit": "recommended" }, 123 | { "id": "oneAPIKit", "label": "Intel® oneAPI Base Toolkit", "urlSlug": "oneapi-kit" }, 124 | { "id": "RenderKit", "label": "Intel® oneAPI Rendering Toolkit", "urlSlug": "render-kit", "baseToolkit": "recommended" }, 125 | { "id": "VTuneProfiler", "label": "Intel® VTune™ Profiler", "urlSlug": "vtune-profiler", "baseToolkit": "recommended" } 126 | ] 127 | ` 128 | -------------------------------------------------------------------------------- /pkg/extractor/tar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package extractor 4 | 5 | import ( 6 | "archive/tar" 7 | "compress/gzip" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | // ExtractTarGz extracts a tar.gz to the destination 14 | func ExtractTarGz(sourcetb string, out string) error { 15 | 16 | //Ensure Output exists 17 | if err := os.MkdirAll(out, 0750); err != nil { 18 | return err 19 | } 20 | 21 | tbz, err := os.Open(sourcetb) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | gzr, err := gzip.NewReader(tbz) 27 | if err != nil { 28 | return err 29 | } 30 | defer gzr.Close() 31 | 32 | tr := tar.NewReader(gzr) 33 | 34 | for { 35 | hdr, err := tr.Next() 36 | 37 | switch { 38 | 39 | case err == io.EOF: 40 | return nil // return when no more files, good path 41 | 42 | case err != nil: 43 | return err 44 | } 45 | 46 | // the target location where the dir/file should be created 47 | target := filepath.Join(out, hdr.Name) 48 | 49 | // check the file type, are we a directory for example 50 | switch hdr.Typeflag { 51 | 52 | case tar.TypeDir: 53 | if err := os.MkdirAll(target, os.FileMode(hdr.Mode)); err != nil { 54 | return err 55 | } 56 | 57 | // we have a file, create it with the stored attr from the header 58 | case tar.TypeReg: 59 | //Sometimes the file can come before its directory listing, or it never has one :S 60 | if !fileExists(filepath.Dir(target)) { 61 | if err := os.MkdirAll(filepath.Dir(target), 0750); err != nil { 62 | return err 63 | } 64 | } 65 | 66 | f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | // Store into destination 72 | if _, err := io.Copy(f, tr); err != nil { 73 | return err 74 | } 75 | 76 | err = f.Close() 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | } 82 | } 83 | 84 | func fileExists(path string) bool { 85 | if _, err := os.Stat(path); os.IsNotExist(err) { 86 | return false 87 | } 88 | return true 89 | } 90 | -------------------------------------------------------------------------------- /pkg/extractor/tar_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package extractor 4 | 5 | import ( 6 | "crypto/sha512" 7 | "encoding/hex" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | ) 13 | 14 | //testdata/golden.tar.gz contents 15 | const testalpha = "testalpha.txt" 16 | const testbeta = "testbeta.txt" 17 | const testdir = "dirtest" 18 | const testalphaSum = "10b8eefa145e6f3ff612197247765c0fa15788874b2f483879c8528383489d97f4ac18daed45334341d55342c94602976b401c88e879a9d8fd950c69040e29e2" 19 | const testbetaSum = "e73842277cc6739947522cc2cac9dc150524d2d6683fe54b79da6a098a2cffc8a9498583c2f17d7897db36fa1980d6d0e729f4989780e0be38ef3684210c5d99" 20 | 21 | func setupGoldTemp(t *testing.T) string { 22 | t.Helper() 23 | dir, err := ioutil.TempDir("", "extar") 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | return dir 28 | } 29 | 30 | func cleanupTemp(t *testing.T, path string) { 31 | t.Helper() 32 | os.RemoveAll(path) 33 | } 34 | 35 | //localhash returns hash, error 36 | func localHash(t *testing.T, path string) string { 37 | t.Helper() 38 | hasher := sha512.New() 39 | local, err := ioutil.ReadFile(path) 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | 44 | _, err = hasher.Write(local) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | 49 | return hex.EncodeToString(hasher.Sum(nil)) 50 | } 51 | 52 | func TestExtractTarGz(t *testing.T) { 53 | 54 | golden := filepath.Join("testdata", "golden.tar.gz") 55 | 56 | tempPath := setupGoldTemp(t) 57 | defer cleanupTemp(t, tempPath) 58 | output := filepath.Join(tempPath, "gold") 59 | 60 | err := ExtractTarGz(golden, output) 61 | if err != nil { 62 | t.Error(err) // Failed to run against golden tar 63 | } 64 | 65 | //test the output of alpha was good 66 | extractedAlphaSum := localHash(t, filepath.Join(output, testalpha)) 67 | if extractedAlphaSum != testalphaSum { 68 | t.Errorf("outputted alpha file does not match expected output sha512 "+ 69 | "golden: %s and we just read: %s", testalphaSum, extractedAlphaSum) 70 | } 71 | 72 | betaCombinedPath := filepath.Join(output, testdir, testbeta) 73 | //test the output of beta was good, this also tests the directory was made! 74 | extractedBetaSum := localHash(t, betaCombinedPath) 75 | if extractedBetaSum != testbetaSum { 76 | t.Errorf("outputted beta file does not match expected output sha512 "+ 77 | "golden: %s and we just read: %s", testbetaSum, extractedBetaSum) 78 | } 79 | 80 | } 81 | 82 | func TestOKTarGz(t *testing.T) { 83 | 84 | oktar := filepath.Join("testdata", "ok.tar.gz") 85 | 86 | tempPath := setupGoldTemp(t) 87 | defer cleanupTemp(t, tempPath) 88 | output := filepath.Join(tempPath, "ok") 89 | 90 | err := ExtractTarGz(oktar, output) 91 | if err != nil { 92 | t.Error(err) // Failed to run against ok-ish tar 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/extractor/testdata/README.md: -------------------------------------------------------------------------------- 1 | golden.tar.gz - A Clean simple TarGZ 2 | ok.tar.gz - A tarball with a non ordered odd header order. 3 | -------------------------------------------------------------------------------- /pkg/extractor/testdata/golden.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/oneapi-cli/a4af422d6ae50511a98cc3900da792a2b5afdb5f/pkg/extractor/testdata/golden.tar.gz -------------------------------------------------------------------------------- /pkg/extractor/testdata/ok.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/oneapi-cli/a4af422d6ae50511a98cc3900da792a2b5afdb5f/pkg/extractor/testdata/ok.tar.gz -------------------------------------------------------------------------------- /pkg/ui/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | package ui 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sort" 13 | "strings" 14 | 15 | "github.com/gdamore/tcell" 16 | "github.com/intel/oneapi-cli/pkg/aggregator" 17 | "github.com/intel/oneapi-cli/pkg/browser" 18 | "github.com/intel/oneapi-cli/pkg/deps" 19 | "github.com/intel/oneapi-cli/pkg/extractor" 20 | "gitlab.com/tslocum/cview" 21 | ) 22 | 23 | const depsMissingEnvFmt = `The sample you have chosen requires the following dependencies: %s 24 | 25 | Unfortunately, we are unable to determine if they are present. 26 | Did you use setvars to configure your environment? Are you using a container build environment?` 27 | 28 | // CLI data 29 | type CLI struct { 30 | sidebar *cview.TextView 31 | app *cview.Application 32 | aggregator *aggregator.Aggregator 33 | userHome string 34 | oneAPIRoot string 35 | home *cview.List 36 | langSelect *cview.List 37 | } 38 | 39 | const idzURL = "https://www.intel.com/content/www/us/en/developer/tools/oneapi/overview" 40 | 41 | func optionViewDocsInBrowser(url string) { 42 | //If it cant open it for some reason, it will silently fail 43 | browser.OpenBrowser(url) 44 | } 45 | 46 | // NewCLI create a new *CLI element for showing the CLI 47 | func NewCLI(a *aggregator.Aggregator, uH string) (cli *CLI, err error) { 48 | if a == nil { 49 | return nil, fmt.Errorf("Aggregator passed not valid") 50 | } 51 | if uH == "" { 52 | return nil, fmt.Errorf("User Home not passed") 53 | } 54 | 55 | oneRootPath, err := deps.GetOneAPIRoot() 56 | if err != nil { 57 | log.Printf("Could not find oneAPI environment, will not check for missing dependencies") 58 | } 59 | app := cview.NewApplication() 60 | 61 | if app == nil { 62 | return nil, fmt.Errorf("Failed to create backend application") 63 | } 64 | return &CLI{app: cview.NewApplication(), aggregator: a, userHome: uH, oneAPIRoot: oneRootPath}, nil 65 | } 66 | 67 | // Show displays the UI 68 | func (cli *CLI) Show() { 69 | 70 | list := cview.NewList(). 71 | AddItem("Create a project", "", '1', func() { 72 | cli.selectLang() 73 | 74 | }).ShowSecondaryText(false). 75 | AddItem("View oneAPI docs in browser", "", '2', func() { 76 | cli.gotoLinkModel() 77 | }). 78 | AddItem("Quit", "Press to exit", 'q', func() { 79 | cli.app.Stop() 80 | }) 81 | 82 | list.SetBorder(true) 83 | 84 | cli.home = list 85 | 86 | //Main Run action! 87 | if err := cli.app.SetRoot(cli.home, true).Run(); err != nil { 88 | log.Fatal(err) 89 | } 90 | } 91 | 92 | func (cli *CLI) gotoLinkModel() { 93 | 94 | optionViewDocsInBrowser(idzURL) 95 | modal := cview.NewModal(). 96 | SetText("View oneAPI docs:\n" + idzURL + "\n"). 97 | AddButtons([]string{"Back"}). 98 | SetDoneFunc(func(buttonIndex int, buttonLabel string) { 99 | cli.app.SetRoot(cli.home, true) 100 | }) 101 | 102 | cli.app.SetRoot(modal, true) 103 | } 104 | 105 | func (cli *CLI) goBackPrj() { 106 | if cli.langSelect != nil { 107 | cli.app.SetRoot(cli.langSelect, true) 108 | return 109 | } 110 | cli.app.SetRoot(cli.home, true) 111 | } 112 | 113 | func (cli *CLI) selectLang() { 114 | if len(cli.aggregator.GetLanguages()) == 1 { 115 | cli.selectProject(cli.aggregator.GetLanguages()[0]) 116 | return 117 | } 118 | 119 | list := cview.NewList().ShowSecondaryText(false) 120 | 121 | var start rune 122 | start = '1' 123 | 124 | for _, k := range cli.aggregator.GetLanguages() { 125 | if len(cli.aggregator.Samples[k]) == 0 { 126 | continue 127 | } 128 | list.AddItem(k, "", start, func() { 129 | lang, _ := list.GetItemText(list.GetCurrentItem()) 130 | cli.selectProject(lang) 131 | }) 132 | start++ 133 | } 134 | list.AddItem("Back", "", 'b', func() { 135 | cli.app.SetRoot(cli.home, true) 136 | }) 137 | list.AddItem("Quit", "Press to exit", 'q', func() { 138 | cli.app.Stop() 139 | }) 140 | list.SetBorder(true).SetTitle("Select sample language") 141 | 142 | cli.langSelect = list 143 | 144 | cli.app.SetRoot(cli.langSelect, true) 145 | } 146 | 147 | func (cli *CLI) selectProject(language string) { 148 | cli.sidebar = cview.NewTextView().SetWordWrap(true). 149 | SetChangedFunc(func() { 150 | cli.app.Draw() 151 | }) 152 | cli.sidebar.SetDynamicColors(true) 153 | 154 | cli.sidebar.Box.SetBorder(true).SetTitle("Description") 155 | 156 | inst := cview.NewTextView() 157 | inst.SetBorder(true) 158 | inst.SetText("Press Backspace to return to previous screen!") 159 | 160 | flex := cview.NewFlex(). 161 | AddItem(cli.tree(language), 0, 1, true). 162 | AddItem(cview.NewFlex().SetDirection(cview.FlexRow). 163 | AddItem(cli.sidebar, 0, 9, false). 164 | AddItem(inst, 3, 0, false), 0, 1, false) 165 | 166 | cli.app.SetRoot(flex, true) 167 | } 168 | 169 | func newSampleNode(s aggregator.Sample) *cview.TreeNode { 170 | node := cview.NewTreeNode(s.Fields.Name).SetSelectable(true) 171 | node.SetReference(s) 172 | return node 173 | } 174 | 175 | // This take a sample and a category to add it to. TODO revist this 176 | func categoriesSeach(parent *cview.TreeNode, cats []string, s aggregator.Sample) { 177 | if len(cats) == 0 { 178 | parent.AddChild(newSampleNode(s)) 179 | return 180 | } 181 | for _, csearch := range parent.GetChildren() { 182 | if csearch.GetText() == cats[0] { 183 | if len(cats) == 1 { 184 | csearch.AddChild(newSampleNode(s)) 185 | return 186 | //continue 187 | } 188 | categoriesSeach(csearch, cats[1:], s) //recurse removing current from array-pop 189 | return 190 | } 191 | } 192 | if len(cats) > 0 { 193 | csearch := cview.NewTreeNode(cats[0]).SetColor(tcell.ColorOrange) 194 | parent.AddChild(csearch) 195 | categoriesSeach(csearch, cats[1:], s) 196 | return 197 | } 198 | } 199 | 200 | type byNodeText []*cview.TreeNode 201 | 202 | func (s byNodeText) Len() int { 203 | return len(s) 204 | } 205 | func (s byNodeText) Swap(i, j int) { 206 | s[i], s[j] = s[j], s[i] 207 | } 208 | func (s byNodeText) Less(i, j int) bool { 209 | return strings.ToLower(s[i].GetText()) < strings.ToLower(s[j].GetText()) 210 | } 211 | 212 | func (cli *CLI) tree(language string) cview.Primitive { 213 | 214 | root := cview.NewTreeNode("Samples"). 215 | SetColor(tcell.ColorOrange) 216 | tree := cview.NewTreeView(). 217 | SetRoot(root). 218 | SetCurrentNode(root) 219 | 220 | var missingCat []*cview.TreeNode 221 | 222 | for _, s := range cli.aggregator.Samples[language] { 223 | if len(s.Fields.Categories) == 0 { 224 | missingCat = append(missingCat, newSampleNode(s)) 225 | continue //skip samples without any categories 226 | } 227 | 228 | //root.AddChild(node) 229 | for _, c := range s.Fields.Categories { 230 | cats := strings.Split(c, "/") 231 | categoriesSeach(root, cats, s) 232 | } 233 | 234 | } 235 | 236 | if len(missingCat) > 0 { 237 | other := cview.NewTreeNode("Other").SetColor(tcell.ColorOrange) 238 | root.AddChild(other) 239 | for _, missingNode := range missingCat { 240 | other.AddChild(missingNode) 241 | } 242 | } 243 | 244 | root.Walk(func(node *cview.TreeNode, parent *cview.TreeNode) bool { 245 | if len(node.GetChildren()) > 1 { 246 | sort.Sort(byNodeText(node.GetChildren())) 247 | } 248 | return true 249 | }) 250 | 251 | tree.SetChangedFunc(func(node *cview.TreeNode) { 252 | reference := node.GetReference() 253 | a, ok := reference.(aggregator.Sample) 254 | if !ok { 255 | 256 | cli.sidebar.Clear() 257 | return 258 | } 259 | var sideTextExtra string 260 | if len(a.Fields.Dependencies) > 0 { 261 | if cli.oneAPIRoot == "" { 262 | sideTextExtra = fmt.Sprintf(depsMissingEnvFmt, a.Fields.Dependencies) 263 | } else { 264 | sideTextExtra, _ = deps.CheckDeps(a.Fields.Dependencies, cli.oneAPIRoot) 265 | } 266 | } 267 | sideTextExtra = cview.Escape(sideTextExtra) 268 | 269 | newText := fmt.Sprintf("%s\n\n[red]%s", a.Fields.Description, sideTextExtra) 270 | cli.sidebar.SetText(newText) 271 | 272 | }).SetSelectedFunc(func(node *cview.TreeNode) { 273 | reference := node.GetReference() 274 | a, ok := reference.(aggregator.Sample) 275 | if !ok { 276 | return 277 | } 278 | cli.askPath(a, language, "") 279 | }) 280 | tree.Box.SetBorder(true).SetTitle("Samples") 281 | tree.SetTopLevel(1) 282 | 283 | tree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 284 | 285 | if event.Key() == tcell.KeyBackspace2 || 286 | event.Key() == tcell.KeyBackspace { 287 | cli.goBackPrj() 288 | } 289 | 290 | return event 291 | 292 | }) 293 | 294 | return tree 295 | } 296 | 297 | func isPathEmpty(path string) bool { 298 | if !aggregator.FileExists(path) { 299 | return true 300 | } 301 | files, err := ioutil.ReadDir(path) 302 | if err != nil { 303 | return true 304 | } 305 | return (len(files) == 0) 306 | } 307 | 308 | func (cli *CLI) askPath(sample aggregator.Sample, language string, path string) { 309 | if path == "" { 310 | pwd, err := os.Getwd() 311 | if err != nil { 312 | log.Println(err) 313 | } 314 | path = filepath.Join(pwd, filepath.Base(sample.Path)) 315 | } 316 | 317 | form := cview.NewForm(). 318 | AddInputField("Destination", path, 55, nil, func(t string) { 319 | path = t 320 | }). 321 | AddButton("Create", func() { 322 | path, err := cli.calcPath(path) 323 | if err != nil { 324 | return 325 | } 326 | if !isPathEmpty(path) { 327 | cli.confirmOverwrite(sample, language, path) 328 | return 329 | } 330 | outPath, err := cli.createProject(sample, language, path) 331 | 332 | if err != nil { 333 | cli.app.Stop() 334 | log.Fatal(err) 335 | } 336 | cli.successModal(outPath) 337 | 338 | }).AddButton("Back", func() { 339 | cli.selectProject(language) 340 | }) 341 | 342 | form.SetBorder(true).SetTitle("Create Project").SetTitleAlign(cview.AlignLeft) 343 | form.SetWrapAround(true) 344 | 345 | cli.app.SetRoot(form, true) 346 | } 347 | 348 | func (cli *CLI) confirmOverwrite(sample aggregator.Sample, language string, path string) { 349 | 350 | text := fmt.Sprintf("Path %s is not empty, Creating the sample may overwrite some files.", path) 351 | buttons := []string{"Back", "Confirm"} 352 | 353 | modal := cview.NewModal(). 354 | SetText(text). 355 | AddButtons(buttons). 356 | SetDoneFunc(func(buttonIndex int, buttonLabel string) { 357 | 358 | if buttonLabel != "Back" { 359 | outPath, err := cli.createProject(sample, language, path) 360 | 361 | if err != nil { 362 | cli.app.Stop() 363 | log.Fatal(err) 364 | } 365 | cli.successModal(outPath) 366 | return 367 | } 368 | cli.askPath(sample, language, path) 369 | }) 370 | cli.app.SetRoot(modal, true) 371 | 372 | } 373 | 374 | func (cli *CLI) successModal(path string) { 375 | 376 | text := fmt.Sprintf("Sucessfully created project in %s", path) 377 | buttons := []string{"Quit"} 378 | printReadmeText := "View Readme and Quit" 379 | 380 | //check for readme, 381 | readme, err := ioutil.ReadFile(filepath.Join(path, "README.md")) 382 | if err == nil { 383 | buttons = append(buttons, printReadmeText) 384 | } else { 385 | readme, err = ioutil.ReadFile(filepath.Join(path, "readme.md")) 386 | if err == nil { 387 | buttons = append(buttons, printReadmeText) 388 | } 389 | } 390 | 391 | modal := cview.NewModal(). 392 | SetText(text). 393 | AddButtons(buttons). 394 | SetDoneFunc(func(buttonIndex int, buttonLabel string) { 395 | cli.app.Stop() 396 | if buttonLabel != "Quit" { 397 | 398 | //The folloiwing is to reset the terminal 399 | s, e := tcell.NewScreen() 400 | if e != nil { 401 | fmt.Fprintf(os.Stderr, "%v\n", e) 402 | os.Exit(1) 403 | } 404 | if e = s.Init(); e != nil { 405 | fmt.Fprintf(os.Stderr, "%v\n", e) 406 | os.Exit(1) 407 | } 408 | s.Clear() 409 | s.Fini() 410 | fmt.Printf("\n") 411 | fmt.Printf("%s\n", string(readme)) 412 | } 413 | }) 414 | cli.app.SetRoot(modal, true) 415 | } 416 | 417 | func (cli *CLI) calcPath(projectPath string) (string, error) { 418 | //Expand env vars the user might have passed through. 419 | projectPath = os.ExpandEnv(projectPath) 420 | //Check if tilda ~ is being used, then use the home dir 421 | if len(projectPath) > 0 && projectPath[0] == '~' { 422 | //userHome comes from the frontend cli but check the HOME is not empty just incase 423 | projectPath = filepath.Join(cli.userHome, projectPath[1:]) //Prepend home path and trim tilda 424 | } 425 | 426 | projectPath, err := filepath.Abs(projectPath) 427 | if err != nil { 428 | return "", err 429 | } 430 | return projectPath, nil 431 | } 432 | 433 | func (cli *CLI) createProject(selectedSample aggregator.Sample, lang string, projectPath string) (output string, err error) { 434 | //Maybe here we might check if the tarball does not exists and the trigger the aggregator to atempt an update 435 | 436 | tarPath, err := aggregator.GetTarBall(cli.aggregator.GetLocalPath(), cli.aggregator.GetURL(), lang, selectedSample.Path) 437 | if err != nil { 438 | return "", err 439 | } 440 | 441 | err = extractor.ExtractTarGz(tarPath, projectPath) 442 | if err != nil { 443 | return "", err 444 | } 445 | return projectPath, nil 446 | } 447 | -------------------------------------------------------------------------------- /pkg/ui/cli_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corporation 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | package ui 4 | 5 | import ( 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "os" 10 | "path/filepath" 11 | "sync" 12 | "testing" 13 | "time" 14 | 15 | "github.com/gdamore/tcell" 16 | "github.com/intel/oneapi-cli/pkg/aggregator" 17 | ) 18 | 19 | func mkTestScreen(t *testing.T, charset string) tcell.SimulationScreen { 20 | t.Helper() 21 | s := tcell.NewSimulationScreen(charset) 22 | if s == nil { 23 | t.Fatalf("Failed to get simulation screen") 24 | } 25 | if e := s.Init(); e != nil { 26 | t.Fatalf("Failed to initialize screen: %v", e) 27 | } 28 | return s 29 | } 30 | 31 | type testNewAggregatorData struct { 32 | cache string 33 | home string 34 | b string 35 | ts *httptest.Server 36 | testLanguages []string 37 | aggregator *aggregator.Aggregator 38 | } 39 | 40 | func setupAggregatorTest(t *testing.T) *testNewAggregatorData { 41 | t.Helper() 42 | var td testNewAggregatorData 43 | 44 | //Get Cache to use 45 | dir, err := ioutil.TempDir("", "example") 46 | if err != nil { 47 | t.Error(err) 48 | } 49 | td.b = dir 50 | td.cache = filepath.Join(dir, "cache") 51 | td.home = filepath.Join(dir, "home") 52 | 53 | // Get a test http server 54 | fs := http.FileServer(http.Dir("testdata")) 55 | ts := httptest.NewServer(fs) 56 | td.ts = ts 57 | 58 | td.testLanguages = []string{"cpp", "python"} 59 | 60 | td.aggregator, err = aggregator.NewAggregator(td.ts.URL, td.cache, td.testLanguages, true, true) 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | 65 | return &td 66 | } 67 | 68 | func (tdata *testNewAggregatorData) cleanup() { 69 | os.RemoveAll(tdata.b) 70 | tdata.ts.Close() 71 | } 72 | 73 | func TestA(t *testing.T) { 74 | td := setupAggregatorTest(t) 75 | defer td.cleanup() 76 | 77 | cli, err := NewCLI(td.aggregator, td.home) 78 | if err != nil { 79 | t.Error(err) 80 | } 81 | 82 | s := mkTestScreen(t, "") 83 | cli.app = cli.app.SetScreen(s) 84 | 85 | var wg sync.WaitGroup 86 | wg.Add(1) 87 | go func() { 88 | defer wg.Done() 89 | cli.Show() 90 | }() 91 | 92 | s.InjectKey(tcell.KeyRune, 'q', tcell.ModNone) 93 | 94 | wg.Wait() 95 | } 96 | 97 | func TestB(t *testing.T) { 98 | td := setupAggregatorTest(t) 99 | defer td.cleanup() 100 | cli, err := NewCLI(td.aggregator, td.home) 101 | if err != nil { 102 | t.Error(err) 103 | } 104 | s := mkTestScreen(t, "") 105 | 106 | cli.app = cli.app.SetScreen(s) 107 | 108 | var wg sync.WaitGroup 109 | wg.Add(1) 110 | go func() { 111 | defer wg.Done() 112 | cli.Show() 113 | }() 114 | 115 | s.InjectKey(tcell.KeyRune, 'q', tcell.ModNone) 116 | 117 | wg.Wait() 118 | } 119 | 120 | func TestFullFlow(t *testing.T) { 121 | td := setupAggregatorTest(t) 122 | defer td.cleanup() 123 | cli, err := NewCLI(td.aggregator, td.home) 124 | if err != nil { 125 | t.Error(err) 126 | } 127 | s := mkTestScreen(t, "") 128 | 129 | cli.app = cli.app.SetScreen(s) 130 | 131 | var wg sync.WaitGroup 132 | wg.Add(1) 133 | go func() { 134 | defer wg.Done() 135 | cli.Show() //Let the CLI run in another routine 136 | }() 137 | 138 | s.InjectKey(tcell.KeyRune, '1', tcell.ModNone) //Main Screen 139 | s.InjectKey(tcell.KeyRune, '1', tcell.ModNone) //Langauge Select 140 | s.InjectKey(tcell.KeyDown, 'd', tcell.ModNone) //Get the zebra 141 | s.InjectKey(tcell.KeyEnter, 'd', tcell.ModNone) //Enter the sample 142 | 143 | ws, err := ioutil.TempDir("", "ws") 144 | if err != nil { 145 | t.Error(err) 146 | } 147 | defer os.RemoveAll(ws) 148 | 149 | //Remove default input with CTRL+U 150 | s.InjectKey(tcell.KeyCtrlU, 'd', tcell.ModNone) 151 | 152 | for i := 0; i < len(ws); i++ { 153 | s.InjectKey(tcell.KeyRune, rune(ws[i]), tcell.ModNone) 154 | time.Sleep(10 * time.Millisecond) //Need to give time to the key presses :/ 155 | } 156 | 157 | s.InjectKey(tcell.KeyTAB, 'd', tcell.ModNone) //Tab 158 | s.InjectKey(tcell.KeyEnter, 'd', tcell.ModNone) //Create 159 | s.InjectKey(tcell.KeyEnter, 'd', tcell.ModNone) //Dismiss success dialog 160 | 161 | wg.Wait() 162 | 163 | //Test for known file in "sample" 164 | 165 | knownZebra := filepath.Join(ws, "this-is-a-zebra.md") 166 | 167 | if !fileExists(t, knownZebra) { 168 | t.Errorf("sample creation flow failed! could not find %s", knownZebra) 169 | } 170 | 171 | } 172 | 173 | func fileExists(t *testing.T, path string) bool { 174 | t.Helper() 175 | if _, err := os.Stat(path); os.IsNotExist(err) { 176 | return false 177 | } 178 | return true 179 | } 180 | -------------------------------------------------------------------------------- /pkg/ui/testdata/cpp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path":"zoo", 4 | "sha":"1", 5 | "example":{ 6 | "name":"zoo-zebra", 7 | "categories":[ 8 | "TestCats" 9 | ], 10 | "description":"It is I, Zebra", 11 | "sample_readme_uri":"https://software.intel" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /pkg/ui/testdata/python.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path":"zoo", 4 | "sha":"1", 5 | "example":{ 6 | "name":"zoo-python", 7 | "categories":[ 8 | "TestCats" 9 | ], 10 | "description":"It is I, Zebra", 11 | "sample_readme_uri":"https://software.intel" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /pkg/ui/testdata/zoo/cpp.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/oneapi-cli/a4af422d6ae50511a98cc3900da792a2b5afdb5f/pkg/ui/testdata/zoo/cpp.tar.gz -------------------------------------------------------------------------------- /pkg/ui/testdata/zoo/python.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/oneapi-cli/a4af422d6ae50511a98cc3900da792a2b5afdb5f/pkg/ui/testdata/zoo/python.tar.gz -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | go test -coverpkg=./... -coverprofile=cover.out ./... 3 | go tool cover -func=cover.out -------------------------------------------------------------------------------- /windowsdata.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/oneapi-cli/a4af422d6ae50511a98cc3900da792a2b5afdb5f/windowsdata.syso -------------------------------------------------------------------------------- /winres.rc: -------------------------------------------------------------------------------- 1 | #define RT_MANIFEST 24 2 | 3 | 1 VERSIONINFO 4 | FILEFLAGSMASK 0X3FL 5 | FILEFLAGS 0L 6 | FILEOS 0X40004L 7 | FILETYPE 0X1 8 | FILESUBTYPE 0 9 | BEGIN 10 | BLOCK "StringFileInfo" 11 | BEGIN 12 | BLOCK "040904B0" 13 | BEGIN 14 | VALUE "LegalCopyright", "(C) Intel Corporation. Available under BSD-3-Clause" 15 | VALUE "OriginalFilename", "oneapi-cli.exe" 16 | END 17 | END 18 | BLOCK "VarFileInfo" 19 | BEGIN 20 | VALUE "Translation", 0x0409, 0x04B0 21 | END 22 | END 23 | --------------------------------------------------------------------------------